美文网首页
【iOS小结】Blocks

【iOS小结】Blocks

作者: WellsCai | 来源:发表于2017-11-07 18:19 被阅读0次

    一. Blocks的认识

    什么是Blocks?

    Blocks是C语言的拓展功能(带有局部变量的匿名函数)。局部变量跟block的关系就像成员变量和类。
    所谓匿名函数,就是不带名称的函数。C语言的标准是不存在这样的函数的。

    //声明一个函数
    int  func( int  count);
    
    //直接调用函数时
    int result = func(10);
    //用函数指针调用函数时
    int result =(* func)(10);
    

    在C语言中,不使用想赋值的函数的名称,就无法取得该函数的地址。而通过Blocks,就能够使用不带名称的函数。

    Block的语法

    完整的Block语法与C语言函数相比有两点不同:没有函数名和带有“^”。

    ^ 返回值类型 参数列表 表达式

    ^ int (int count){return  count + 1;}
    

    但是Block语法可以省略返回值类型和参数列表。省略返回值类型时,如果表达式中有return语句就使用该返回值的类型,如果没有return语句就使用void类型。


    Block语法省略返回值类型和参数列表.png
    //省略后的Block
    ^{printf("Blocks\n");}
    
    Blocks的使用

    我们先来看看C语言函数的使用:

    int func(int count){
      return count + 1;
    }
    //将函数地址赋值给函数指针类型变量
    int (*funcptr)(int) = &func;
    

    声明Block类型变量以及变量之间的赋值:

    int (^blk)(int)  = ^(int count){return count + 1};
    int (^blk1)(int);
    blk1 = blk;
    

    Block类型变量作为函数参数和函数返回值:

    int (^func())(int){
      return ^(int count){return count + 1};
    }
    

    使用typedef声明Block类型变量

    typedef int (^blk_t)(int);
    
    int func(blk_t blk){
      return blk(10);
    }
    

    也可以像C语言变量一样使用,使用Block的指针类型变量。

    typedef int (^blk_t)(int);
    blk_t blk = ^(int count){return count + 1};
    blk_t *blkptr = &blk;
    (*blkptr)(10);
    
    Block截获的自动变量值

    Block表达式截获所使用的自动变量的值是该自动变量的瞬间值,不能在Block中改变(编译器会报错),只能输出该瞬间值。如果在Block中修改该值,要加上__block修饰符。

    int let = 10;
    __block int var = 20;
    void (^blk)(void) = ^{
      printf("%d,%d",let,var);  //输出结果 10,21
      var = 22;                //这边也可以改变var的值,而let不可以
    };
    let = 11;      
    var = 21;
    blk();
    

    二. Block的底层实现

    通过clang(LLVM编译器)将Block语法的源代码转换为C++源代码来了解Block的底层实现。

    • 在Xcode创建Block.m文件
    • 打开终端,cd到存放Block.m的文件夹
    • 输入clang -rewrite-objc Block.m,生成Block.cpp
    int mian(){
        void (^blk)(void) = ^{
            printf("Block");
        };
        blk();
        return 0;
    }
    

    打开.cpp拉到最后面,这些才是我们转换后的源码:

    struct __block_impl {
      void *isa;      //对象都有的isa指针,指向元类
      int Flags;      //标志
      int Reserved;   //今后版本升级所需的区域
      void *FuncPtr;  //函数指针
    };
    
    /* block结构体 */
    struct __mian_block_impl_0 {
       struct __block_impl impl;           
       struct __mian_block_desc_0* Desc;  
     
       // block构造函数 
       __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int flags=0) {
         impl.isa = &_NSConcreteStackBlock;   
         impl.Flags = flags;
         impl.FuncPtr = fp;
         Desc = desc;
      }
    };
    
    /* block中的代码 */
    //__cself相当于Objective-C中的self(用OC类的说法就是self 执行__mian_block_func_0)
    static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
            printf("Block");
     }
    
    static struct __mian_block_desc_0 {
      size_t reserved;     //今后版本升级所需的区域
      size_t Block_size;   //Block的大小
    } __mian_block_desc_0_DATA = {     //这部分为赋值
      0, 
      sizeof(struct __mian_block_impl_0)
    };
    
    /*  mian函数  */
    int mian(){
        void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA));
    
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    
        return 0;
    }
    

    我们先看看main函数里面的代码,我们可简化成以下代码:

    //创建Block
    struct __mian_block_impl_0  temp = __mian_block_impl_0(__mian_block_func_0, &__mian_block_desc_0_DATA));
    struct __mian_block_impl_0  *blk = &tmp;
    
    //执行Block
    (*blk->impl.FuncPtr)(blk);
    

    该源代码将__mian_block_impl_0结构体类型的自动变量,即栈上生成的__mian_block_impl_0结构体实例的指针,赋值给__mian_block_impl_0结构体指针类型的变量blk。通俗地讲,就是将__mian_block_impl_0结构体实例的指针赋给变量blk。
    __mian_block_impl_0构造函数(即源代码中的Block构造函数)的第一个参数是Block语法转换的C语言指针,第二个参数是作为静态全局变量初始化的__mian_block_desc_0结构体实例指针。
    我们再来看执行Block的部分,这是简单地使用函数指针调用函数。在构造函数中,由Block语法转换的__mian_block_func_0函数的指针被赋值给成员变量FuncPtr,并以Block自身为参数。
    补充重要的一点,前面提到的:

     impl.isa = &_NSConcreteStackBlock;    
    

    其实,block就是Objective-C对象。这边isa指针指向的_NSConcreteStackBlock,该Block的信息放置于_NSConcreteStackBlock中(相当于子类和父类的关系)。

    截获自动变量值

    我们先来看看截获自动变量值的源代码,理解自动变量值为什么只是一个瞬间值。
    Objective-C代码:

    int mian(){
        int val = 10;
        void (^blk)(void) = ^{
            printf("val = %d",val); //输出结果 val= 10
        };
        val = 11;
        blk();
        return 0;
    }
    

    clang后的源代码:

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    / * Block结构体 */
    struct __mian_block_impl_0 {
      struct __block_impl impl;
      struct __mian_block_desc_0* Desc;
      int val;            //比之前的结构多出该成员变量val
      /* 构造函数 */
      __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    / * Block中执行的函数 */
    static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
        int val = __cself->val;     // bound by copy
        printf("val = %d",val);     //打印的是Block自己的成员变量的值
    }
    
    static struct __mian_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __mian_block_desc_0_DATA = { 0, sizeof(struct __mian_block_impl_0)};
    
    int mian(){
        int val = 10;
    
        //构造函数中传入val的值
        void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, val));
    
        val = 11;
    
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    
        return 0;
    }
    

    通过源码,我们看出,Block语法表达式中使用的自动变量被作为成员变量追加到__mian_block_impl_0结构体中,并且会从构造函数中传入val的值作为参数。改写该值报编译错误的原因我们也由想而知。

    struct __mian_block_impl_0 {
      struct __block_impl impl;
      struct __mian_block_desc_0* Desc;
      int val;            //比之前的结构多出该成员变量val
      /* 构造函数 */
      __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    在后面的输出函数中,也是输出Block的成员变量val的值。由此可见,在__mian_block_impl_0结构体实例中,自动变量值被截获。

    static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
        int val = __cself->val;     // bound by copy
        printf("val = %d",val);     //打印的是Block自己的成员变量的值
    }
    
    __blcok说明符的值

    那如何才能改变Block中截获的自动变量?有两种方法:
    ① C语言的静态变量、静态全局变量、全局变量允许Block改写值

    int global_val = 1; //全局变量
    static int static_global_val = 2; //静态全局变量
    
    int mian(){
        static int static_val = 10;  //静态变量
        void (^blk)(void) = ^{
            global_val *= 1;
            static_global_val *= 2;
            static_val *=3;
        };
        blk();
        return 0;
    }
    

    转换后的源码为:

    int global_val = 1;
    static int static_global_val = 2;
    
    struct __mian_block_impl_0 {
      struct __block_impl impl;
      struct __mian_block_desc_0* Desc;
      int *static_val;    //保存的是静态变量的指针
      __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
      int *static_val = __cself->static_val; // bound by copy
            global_val *= 1;
            static_global_val *= 2;
            (*static_val) *=3;
        }
    
    static struct __mian_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __mian_block_desc_0_DATA = { 0, sizeof(struct __mian_block_impl_0)};
    
    int mian(){
        static int static_val = 10;
        void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, &static_val));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
        return 0;
    }
    

    对静态全局变量static_global_val和全局变量global_val的访问和转换前完全相同。
    而静态变量static_val,是使用其指针进行访问的。将静态变量static_val的指针传递给__mian_block_impl_0结构体的构造函数并保存。那为什么自动变量不用指针访问的方法呢?因为自动变量存放在栈帧中,变量作用域结束的同时,自动变量被废弃,Block将不能用指针访问到原来的自动变量。

    ② 使用__block说明符
    __block说明符类似于static、auto和register说明符,它们用于指定将变量值设置到哪个存储域中。例如,auto表示作为自动变量存储在栈中,static表示作为静态变量存储在数据区中。

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

    转换后的源代码:

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    /* __block变量生成的结构体 */
    struct __Block_byref_val_0 {
     void *__isa;
     __Block_byref_val_0 *__forwarding;  
     int __flags;
     int __size;
     int val;     //保存的val的值
    };
    
    struct __mian_block_impl_0 {
      struct __block_impl impl;
      struct __mian_block_desc_0* Desc;
      __Block_byref_val_0 *val; // by ref
    
      __mian_block_impl_0(void *fp, struct __mian_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 __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
      __Block_byref_val_0 *val = __cself->val; // bound by ref
    
            (val->__forwarding->val) = 1;
    }
    
    static void __mian_block_copy_0(struct __mian_block_impl_0*dst, struct __mian_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __mian_block_dispose_0(struct __mian_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __mian_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __mian_block_impl_0*, struct __mian_block_impl_0*);
      void (*dispose)(struct __mian_block_impl_0*);
    } __mian_block_desc_0_DATA = {
       0, 
      sizeof(struct __mian_block_impl_0), 
      __mian_block_copy_0, 
      __mian_block_dispose_0
    };
    
    int mian(){
        //创建__Block_byref_val_0结构体实例
        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
     
        //将__Block_byref_val_0结构体指针作为参数
        void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    
        return 0;
    }
    

    加上__block说明符,我们发现__block变量同Block一样变成__Block_byref_val_0结构体类型的自动变量。__Block_byref_val_0结构体的地址作为参数传入Block的构造函数中。因此,Block的__mian_block_impl_0结构体实例持有指向__block变量的__Block_byref_val_0结构体实例的指针。
    至于__Block_byref_val_0结构体为什么在Block外创建,是为了方便多个Block使用。

    struct __Block_byref_val_0 {
     void *__isa;
     __Block_byref_val_0 *__forwarding;  //指向自身的指针
     int __flags;
     int __size;
     int val;         //保存的val的值
    };
    

    在给__bloc变量赋值的源码中:

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

    __Block_byref_val_0结构体实例的成员变量__forwarding指向该实例自身的指针。通过成员变量__forwarding访问成员变量val。(这边有点绕,要弄懂先要弄清楚Block的存储域)

    访问__block变量.png
    Block的存储域

    Block转换为Block的结构体类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。那Block超出变量作用域是如何继续存在的呢?
    要了解这点,我们要先知道Block的三种类型以及它们的存储域:

    • _NSConcreteStackBlock
    • _NSConcreteGlobalBlock
    • _NSConcreteMallocBlock
    不同类型Block的存储域.png

    通过声明全局变量blk来使用Block语法,那该Block类为_NSConcreteGlobalBlock。因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量的截获。因此将Block用结构体实例设置在与全局变量相同的数据区域中即可。另外,在函数内而不在记述广域变量的地方使用Block语法时,只要Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区域。
    综上,以下情况Block为_NSConcreteGlobalBlock类对象,除此之外的Block语法生成的Block为_NSConcreteStackBlock类对象,且设置在栈上。

    • 记述全局变量的地方有Block语法时
    • Block语法的表达式中不使用应截获的自动变量时

      配置在全局变量的Block,从变量作用域外也可以通过指针安全地使用。但设置在栈上的Block,如果所属的变量作用域结束,该Block和__block变量都会被废弃。针对这个问题,Blocks提供了将Block和__block变量从栈上复制到堆上的方法。 从栈复制到堆上的Block与__block变量.png
    typedef int (^blk_t)(int)
    
    blk_t func(int rate){
      return ^(int count){return rate * count};
    }
    
    blk_t func(int rate){
      blk_t tmp = &__func_block_impl_0(__func_block_func_0,&__func_block_desc_0_DATA,rate);
    /*
     * 将配置在栈上的Block的结构体实例赋值给tmp
     */
    
      tmp = objc_retainBlock(tmp);  
    /*
     * 等价于tmp = _Block_copy(tmp)
     * 将栈上的Block赋值到堆上,并将堆上的地址作为指针赋值给tmp
     */
    
      return objc_autoreleaseReturnValue(tmp);
     /*
      * 将堆上的Block对象注册到autoreleasepool并返回
      */
    }
    

    将Block作为函数返回值时,编译器会自动生成复制到堆上的代码。当向方法或函数的参数中传递Block时,需要我们手动生成代码,调用Block的copy方法。

    Block的Copy.png

    不过以下方法或函数不用手动调用:

    • Cocoa框架的方法且方法名中含有usingBlock等时(比如NSArray类的enumerateObjectsUsingBlock实例方法)
    • GCD的API

    当使用__block变量的Block从栈上复制到堆上时,这些__block变量也全部被从栈复制到堆,Block持有__block变量。

    Block持有__blcok变量.png Block的废弃和__block变量的释放.png

    使用__block变量的Block持有__block变量(__block变量作为Block的一个成员变量)。如果Block被废弃,它所持有的__block变量也就被释放。栈上的__block用结构体实例在从栈复制到堆时,会将成员变量__forwarding的值替换为复制目标堆上的__block用结构体实例的地址。这样不管__block变量配置在栈上还是堆上,都可以正确访问该变量。

    复制__block变量.png
    __block int val = 0;
    void (^blk)(void) = [^{++val;}  copy];
    ++val;
    blk();
    

    两个++val均可转换成以下源码:

    ++(val.__forwarding->val);
    

    第一个val为复制到堆上的__block变量用结构体实例,会通过指针访问到自己。第二个val为复制前栈上__block变量用结构体实例,通过指针访问到堆上的__block变量用结构体实例。通过该功能,都可以顺利访问同一个__block变量。

    截获对象

    前面我们截获的是普通值或带__block说明符的值,那要是在Block语法中使用对象呢?

    typedef void(^blk_t)(id obj);
    blk_t blk;
    void setupBlk(){
        id array = [[NSMutableArray alloc] init];
        blk = [^(id obj){
            [array addObject:obj];
            NSLog(@"array count = %ld",[array count]);
        } copy];
    }
    int main(){
        setupBlk();
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
        return 0;
    }
    

    输出结果为:

    array count = 1
    array count = 2
    array count = 3
    

    本来array的变量域结束的同时,array被废弃,其强引用失效,因此赋值给变量array的NSMutableArray类的对象必定被释放并废弃。但是代码运行正常,由此可见赋值给array的NSMutableArray类的对象在最后Block的执行部分超出其变量作用域而存在。通过编译器转换后的源代码如下:

    typedef void(*blk_t)(id obj);
    blk_t blk;
    
    struct __setupBlk_block_impl_0 {
      struct __block_impl impl;
      struct __setupBlk_block_desc_0* Desc;
      id array;
      /* 构造方法 */
      __setupBlk_block_impl_0(void *fp, struct __setupBlk_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __setupBlk_block_func_0(struct __setupBlk_block_impl_0 *__cself, id obj) {
            id array = __cself->array; // bound by copy
            ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_9v_3rmg01ds0zj0gpwn04shlxwc0000gn_T_Block_097da3_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
        }
    
    static void __setupBlk_block_copy_0(struct __setupBlk_block_impl_0*dst, struct __setupBlk_block_impl_0*src) {
      //相当于retain方法
      _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    static void __setupBlk_block_dispose_0(struct __setupBlk_block_impl_0*src) {
      //相当于release方法
      _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    static struct __setupBlk_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __setupBlk_block_impl_0*, struct __setupBlk_block_impl_0*);
      void (*dispose)(struct __setupBlk_block_impl_0*);
    } __setupBlk_block_desc_0_DATA = {
       0,
       sizeof(struct __setupBlk_block_impl_0),
       __setupBlk_block_copy_0, 
      __setupBlk_block_dispose_0
    };
    
    void setupBlk(){
        id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
        blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__setupBlk_block_impl_0((void *)__setupBlk_block_func_0, &__setupBlk_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
    }
    
    int main(){
        setupBlk();
    
        ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
        ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
        ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    
        return 0;
    }
    

    我们可以发现,Block结构体中截获__strong修饰符的成员变量array:

    struct __setupBlk_block_impl_0 {
      struct __block_impl impl;
      struct __setupBlk_block_desc_0* Desc;
      id array;  //等同于id  __strong array
    }
    

    在ARC需要遵守的规则中,对象型变量不能作为C语言结构体的成员变量。因为编译器不知道何时进行C语言结构体的初始化和废弃,不能很好地管理内存。但是Objective-C的运行时库能够准确把握Block从栈复制到堆以及堆上的Block被废弃的时机,因此Block用结构体中即使含有附有__strong或__weak修饰符的变量,也能恰当地进行初始化和废弃。
    在该Block源码中,__setupBlk_block_desc_0中增加了copy和dispose函数指针的成员变量,以及作为指针赋值给该成员变量的__setupBlk_block_copy_0和__setupBlk_block_dipose_0方法。
    __setupBlk_block_copy_0函数调用相当于retain实例方法的函数,将对象赋值在对象类型的结构体成员变量中,__setupBlk_block_dipose_0函数调用相当于release实例方法的函数,释放赋值在对象类型的结构体成员变量中的对象。简单说,就是对Block结构体中成员变量array赋值和废弃。当然,在Block从栈复制到堆时以及堆上的Block被废弃时才会调用这些函数。

    如果在在array前面加上__block说明符呢?
    转换的源码如下:

    struct __Block_byref_array_0 {
      void *__isa;
    __Block_byref_array_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     id array;
    };
    
    struct __setupBlk_block_impl_0 {
      struct __block_impl impl;
      struct __setupBlk_block_desc_0* Desc;
      __Block_byref_array_0 *array; 
    
      __setupBlk_block_impl_0(void *fp, struct __setupBlk_block_desc_0 *desc, __Block_byref_array_0 *_array, int flags=0) : array(_array->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __setupBlk_block_copy_0(struct __setupBlk_block_impl_0*dst, struct __setupBlk_block_impl_0*src{
      _Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    
    static void __setupBlk_block_dispose_0(struct __setupBlk_block_impl_0*src) {
      _Block_object_dispose((void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    

    同Block截获对象一样,当__block变量从栈复制到堆时,使用_Block_object_assign函数,持有赋值给__block变量的对象。当堆上的__block变量被废弃时,使用_Block_object_dispose函数,释放赋值给__blcok变量的对象。
    由此可见,只要堆上的Block的成员变量(__block变量或者对象)存在,那么该对象就会继续处于被持有状态。

    如果是__weak和__block的组合呢?
    当超出作用域时,__weak的对象也会置于nil。所以会得出结果:

    array count = 0
    array count = 0
    array count = 0
    

    如果不调用copy方法呢?
    执行该代码后,程序会强制结束。因为只有调用_Block_copy才能持有截获的附有__strong修饰符的对象类型的自动变量值,否则即使截获了对象,没有retain,它也会随着变量作用域的结束而被废弃。
    所以,Block中使用对象类型自动变量时,除以下情形外,需要调用Block的copy方法。

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

    三. Block的循环引用

    如果在Block中使用__strong 修饰符的对象类型自动变量,那么当Block从栈复制到堆时,该对象为Block所持有。这样容易引起循环引用。

    typedef void(^blk_t)(void);
    @interface MyObject()
    {
        blk_t _blk;
    }
    @end
    @implementation MyObject
    - (instancetype)init{
        self = [super init];
        _blk = ^{NSLog(@"self = %@",self);};
        return self;
    }
    - (void)dealloc{
        NSLog(@"dealloc");
    }
    
    //在main函数创建MyObject
    int main(){
        id obj = [[MyObject alloc] init];
        return 0;
    }
    

    MyObject类对象的成员变量_blk持有Block的强引用,即MyObject对象持有Block。由于Block是赋值给成员变量_blk,Block会由栈复制到堆,并持有所使用的self。所以造成循环引用。

    使用Block成员变量循环引用.png

    那避免循环引用有哪些方法呢?
    ① 声明附有__weak修饰符的变量,并将self赋值使用。

    - (instancetype)init{
        self = [super init];
        id __weak tmp = self;
        _blk = ^{NSLog(@"self = %@",tmp);};
        return self;
    }
    
    __weak避免循环引用.png

    ②使用__block变量(可用于ARC无效时,不过缺点是必须执行Block,将临时变量tmp置为nil)

    - (instancetype)init{
        self = [super init];
        id __block tmp = self;
        _blk = ^{
            NSLog(@"self = %@",tmp);
            tmp = nil;
        };
        return self;
    }
    
    __block避免循环引用.png

    还有一点需要注意的时,__block说明符解决循环引用时的方式在ARC和MRC是不一样的。MRC中,Block从栈复制到堆时,Block使用的变量为附有__block说明符的id类型或对象类型的自动变量,不会被retain,若没有附有__block,会被retain。所以以下可以解决循环引用。

    - (instancetype)init{
        self = [super init];
        id __block tmp = self;
        _blk = ^{
            NSLog(@"self = %@",tmp);
        };
        return self;
    }
    

    相关文章

      网友评论

          本文标题:【iOS小结】Blocks

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