Blocks

作者: 康大侠 | 来源:发表于2017-08-02 14:20 被阅读3次

    参考:Objective-C高级编程 iOS与OS X多线程和内存管理

    1.1 Blocks概要

    1.1.1 什么是Blocks

    • BlocksC语言的扩充功能,用一句话表示 带有自动变量(局部变量)的匿名函数
    • Blocks中将匿名函数部分称为Block literal,或简称为Block

    1.2 Blocks模式

    1.2.1 Block语法

    不在本文讨论范围内,不做详细说明

    1.2.2 Block类型变量

    声明Block类型变量的示例如下: int (^blk)(int); 该Block类型变量与一般的C语言变量完全相同,可以作为以下用途

    > 自动变量
    > 函数参数
    > 静态变量
    > 静态全局变量
    > 全局变量
    

    typedef int (^blk_t)(int);通过typedef可声明blk_t类型变量

    1.2.3 截获自动变量值

    带有自动变量值在Blocks中表现为截获自动变量值

    int main()
    {
       
        int val = 10;
        
        const char *fmt = "val = %d\n";
        
        void(^blk)(void) = ^{printf(fmt,val);};
        
        val = 2;
        
        fmt = "These values were changed .val = %d\n";
        
        blk();
        
        return 0;
    } 
    

    打印结果:val = 10

    Blocks中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。所以执行Block语法后,即使改变了Block中使用的自动变量的值也不会影响到Block执行时自动变量的值.

    1.2.4 _ _block说明符

    若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符

    1.2.5 截获的自动变量

    int main()
    {
        id array = [[NSMutableArray alloc] init];
        
        void (^blk)(void) = ^{
            id obj = [[NSObject alloc] init];
            
            [array addObject:obj];
        };
        
        return 0;
    }
    

    这是没有问题的,而面向截获的array赋值则会产生编译错误。虽然赋值给截获的自动变量array的操作会产生编译错误,但使用截获的值却不会有任何问题。

    另外,在使用C语言数组时必须小心使用其指针,源代码示例如下:

    int main()
    {
        const char text[] = "hello";
        
        void(^blk)(void) = ^{printf("%c\n",text[2]);};
        
        return 0;
    }
    

    只是使用C语言的字符串字面量数组,而并没有想截获的自动变量赋值,因此看似没有问题,但是实际上产生了编译错误。原因在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获,这时,使用指针就可以解决问题:

    int main()
    {
        const char *text = "hello";
        
        void(^blk)(void) = ^{printf("%c\n",text[2]);};
        
        blk();
        
        return 0;
    }
    

    1.3 Blocks的实现

    1.3.1 Block的实质

    我们首先通过clang(LLVM编译器)将含有Block语法的源代码变换成C++的源代码(说是C++,其实也仅是使用了Struct结构,其本质是C语言源代码)。
    转换语句clang -rewrite-objc 源代码文件名

    下面我们转换Block语法

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      //构造函数
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
            //_NSConcreteStackBlock用于初始化_block_impl结构体的isa成员
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
    {
        printf("Block\n");
    }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    int main()
    {
        void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    
        return 0;
    }
    

    下面分成几部分逐步理解,首先来看看最初的源代码中Block语法。
    ^{printf("%c\n",text[2]);};
    可以看到,变换后的源代码中也含有相同的表达式

     //参数__cself为指向Block值的变量(类似于Objective-C实例方法中指向对象自身的变量self)
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
    {
        printf("Block\n");
    }
    

    如变换后的源代码所示,通过Blocks使用的匿名函数实际上被作为简单的C语言函数来处理.另外,根据Block语法所属的函数名(此处为main)和该Block语法在该函数出现的顺序值(此处为0)来给经clang变换的函数命名。

    这里可能要进行补充

    先来看看构造函数的调用

    void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
    

    因为转换较多,看起来不是很清楚,所以我们去掉转换的部分,具体如下:

        struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
        struct __main_block_impl_0 *blk = &tmp;
    
    

    以下为这部分代码最初的源代码

    void(^blk)(void) = ^{printf("Block\n");};
    

    我们来确认一下使用该Block的部分

    blk()
    

    这部分可变换为以下源代码

     ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    

    去掉转换部分

    //static void __main_block_func_0(struct __main_block_impl_0 *__cself) 实际上是使用这个函数的指针调用函数
    (*blk -> impl.FuncPtr)(blk);
    

    这就是简单的使用函数指针调用函数。由Block语法转换的__main_block_func_0函数的指针被赋值到成员变量FuncPtr中。另外也说明了,__main_block_func_0函数的参数__cself指向Block值。在调用该函数的源代码中可以看出Block正是作为参数进行了传递。

    下面对_NSConcreteStackBlock进行说明

    //将Block指针赋给Block的结构体成员变量isa
     impl.isa = &_NSConcreteStackBlock;
    

    其实,所谓Block就是Objective-C对象。
    id这一变量类型用于存储Objective-C对象。在Objective-C源代码中,虽然可以向使用Void *类型那样随意的使用id,但此id类型也能够在C语言中声明。在/usr/include/objc/runtime.h中是如下声明的

    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    ///Class为objc_class结构体的指针类型
    typedef struct objc_class *Class;
    
    
    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    
    /// id为objc_object结构体的指针类型
    typedef struct objc_object *id;
    

    Objective-C中由类生成对象意味着,像结构体这样生成由该类生成的对象的结构体实例。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针。

    _NSConcreteStackBlock相当于class_t结构体实例

    1.3.2 截获自动变量值

    将截获自动变量值的源代码通过clang进行转换

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      const char *fmt;
      int val;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
    {
      const char *fmt = __cself->fmt; // bound by copy
      int val = __cself->val; // bound by copy
      printf(fmt,val);
    }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    int main()
    {
        int val = 10;
        const char *fmt = "val = %d\n";
        void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
    
        val = 2;
        fmt = "These values were changed. val = %d\n";
    
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    
        return 0;
    }
    
    

    首先我们注意到,Block语法表达式中使用的自动变量被作为成员变量追加到__main_block_impl_0结构体中。

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      const char *fmt;
      int val;
    };
    

    下面来看看初始化该结构体的构造函数

    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) 
    

    构造函数调用结果如下

    {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
        fmt = "val = %d\n";
        val = 10;
      }
    

    由此可知,在__main_block_impl_0结构体实例(Block)中,自动变量值被截获。
    下面来看一下使用Block的匿名函数的实现

    ^{printf(fmt,val);}
    

    该源代码可转换为以下函数

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
    {
      const char *fmt = __cself->fmt; // bound by copy
      int val = __cself->val; // bound by copy
      printf(fmt,val);
    }
    

    总得来说,所谓截获自动变量意味着在执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block的结构体实例(即Block自身)中。

    1.3.3 _ _block说明符

    int main()
    {
        __block int val = 10;
        
        void(^blk)(void) = ^{val = 1;};
        
        return 0;
    }
    
    

    clang转换代码后

    struct __Block_byref_val_0 {
      void *__isa;
    __Block_byref_val_0 *__forwarding;
     int __flags;
     int __size;
     int val;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_val_0 *val; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
    {
       __Block_byref_val_0 *val = __cself->val; // bound by ref
        (val->__forwarding->val) = 1;
    }
    
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) 
    {
        _Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) 
    {
        _Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    int main()
    {
        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
            (void*)0,
            (__Block_byref_val_0 *)&val,
            0,
            sizeof(__Block_byref_val_0),
            10
        };
    
        void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    
        return 0;
    }
    

    其中__block int val = 10;转换为结构体实例,即栈上生成的__Block_byref_val_0结构体实例

    __Block_byref_val_0 val = {
            (void*)0,
            (__Block_byref_val_0 *)&val,
            0,
            sizeof(__Block_byref_val_0),
            10
        };
    

    而且这个值出现在结构体实例的初始化中,这意味着该结构体持有相当于原自动变量的成员变量。该结构体声明如下:

    struct __Block_byref_val_0 {
      void *__isa;
    __Block_byref_val_0 *__forwarding;
     int __flags;
     int __size;
     int val;
    };
    

    下面这段给__Block变量赋值的代码^{val = 1;}该源码转换如下:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
    {
       __Block_byref_val_0 *val = __cself->val; // bound by ref
        (val->__forwarding->val) = 1;
    }
    

    Block__main_block_impl_0结构体实例持有指向__block变量的__Block_byref_val_0结构体实例指针。__Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过成员变量__forwarding访问成员变量val.

    下面主要说明两个部分
    1.Block超出变量作用域可存在的理由
    2.__block变量的结构体成员变量_forwarding存在的理由
    有时在__block变量配置在堆上的状态下,也可以访问栈上的__block变量。在此情形下,只要栈上的结构体实例成员变量__forwarding指向堆上的结构体实例,就可以正确进行访问。

    1.3.4 Block存储域

    名称 实质
    Block 栈上Block的结构体实例
    __block变量 栈上__block的结构体实例

    将Block当作Objective-C对象来看时,该Block的类为_NSConcreteStackBlock

    设置对象的存储域
    _NSConcreteStackBlock
    _NSConcreteGlobalBlock 程序的数据区域(.data区)
    _NSConcreteMallocBlock

    只在截获自动变量时,Block用结构体实例截获的值才会根据执行时的状态变化

    向方法或函数的参数中传递Block时需要手动复制Block,但以下方法或函数不需要

     * Cocoa框架的方法名中含有usingBlock等时
     * GCD的API
    

    将Block从栈上复制到堆上是相当消耗CPU的,因此只在此情形下让编程人员手动进行复制。

    Block 的副本

    Block 的类 副本源的配置存储域 复制效果
    _NSConcreteStackBlock 从栈复制到堆
    _NSConcreteGlobalBlock 程序的数据区域(.data区) 什么也不做
    _NSConcreteMallocBlock 引用计数增加

    1.3.5 _ _block变量存储域

    Block 从栈复制到堆时对__block变量产生的影响

    __block变量的配置存储域 Block从栈复制到堆时的影响
    从栈复制到堆并被Block持有
    被Block持有
    
    int main()
    {
        __block int val = 10;
        
        //这个val使用的是复制到堆上的__block变量
        void(^blk)(void) = [^{++val;} copy];
        
         //为复制前栈上的__block变量用的结构体实例,但__forwarding 指向目标堆上的__block变量用结构体实例的地址
        ++val;
        
        blk();
        
        NSLog(@"%d",val);
        
        return 0;
    }
    
    

    1.3.6 截获对象

    除了以下情形外,推荐使用Blockcopy实例方法

    • Block 作为函数返回值返回时
    • 将Block赋值给类的附有__strong修饰符的id类型或Block类型成员变量时
    • 向方法名中含有usingBlock的cocoa框架方法或GCD的API中传递Block时

    相关文章

      网友评论

        本文标题:Blocks

        本文链接:https://www.haomeiwen.com/subject/fdoelxtx.html