Blocks的实现

作者: wilsonhan | 来源:发表于2018-06-11 21:04 被阅读0次

    Blocks是“带有自动变量值的匿名函数”。本文通过Blocks的实现来理解Blocks。

    本文目录

    1. Blocks的实质
    2. 截获自动变量
    3. 修改Block外部变量的两种方式
    4. Block存储域
    5. 截获对象
    6. __block变量和对象
    7. Block循环引用
    8. copy/release

    使用工具:clang(LLVM编译器)将OC代码转换成可读的源代码。

    clang -rewrite-objc 源代码文件名
    

    Block的实质

    Block的实质就是OC对象,Block函数代码实际上被作为简单的C语言函数来处理。

    首先写一段最简单的Blocks代码

    int main(int argc, const char * argv[]) {
        //声明并定义一个block对象
        void (^blk)(void) = ^{
            printf("Hello,world!\n");
        };
        //调用block对象
        blk();
        return 0;
    }
    

    转换成C代码之后是这样,Block实际上是由结构体声明的。

    struct __block_impl {
      void *isa;//这里与OC中类对象一样,指针指向的是类对象
      int Flags;//标志
      int Reserved;//版本升级所需要的区域
      void *FuncPtr;//函数指针
    };
    
    static struct __main_block_desc_0 {
      size_t reserved;//保留区域
      size_t Block_size;//Block的大小
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    //结构体__block_impl和__main_block_desc_0组成了最简单的block结构体__main_block_impl_0
    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) {
        impl.isa = &_NSConcreteStackBlock;//定义block的类型,总共有三种,全局的静态block,栈中的block,堆中的block。
        impl.Flags = flags;//初始化flag
        impl.FuncPtr = fp;//传递函数地址
        Desc = desc;//初始化__main_block_desc_0结构体
      }
    };
    
    //block中的函数声明
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            printf("Hello,world!\n");
        }
    
    //main函数
    int main(int argc, const char * argv[]) {
        //定义一个void *的blk指针,等号右边是使用__main_block_impl_0的构造函数进行初始化,传入的第一个参数是函数指针,第二个参数是描述信息结构体
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
        //调用函数指针,执行函数
        //(void (*)(__block_impl *))这部分是将FuncPtr转换成该类型的函数指针,返回值为void *,传参为void,编译器会在传参前添加一个传递结构体自身的指针
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
        return 0;
    }
    

    通过上面的源代码,可以很清晰的了解到Block是如何实现的,而在Block的结构体__block_impl中,发现了isa指针,与基于objc_object结构体的OC类对象结构体一样,所以,Block其实就是一个OC对象。

    截获自动变量

    再来看稍微复杂一点的代码

    int main(int argc, const char * argv[]) {
        
        int dmy = 256;//没有被block使用到的变量
        int val = 10;//被block捕获的变量
        const char *fmt = "val = %d\n";//被block捕获的变量
        void (^blk)(void) = ^{
            printf(fmt, val);//使用fmt字符串打印val变量
        };
        //定义完block对象后,首先修改val变量,看修改后block区块里捕获的对象是否也修改
        val = 2;
        //同样修改fmt指针指向的常量字符串
        fmt = "These values were changed. val = %d\n";
        //调用block函数blk()
        blk();
    
        return 0;
    }
    

    转换之后的C代码

    struct __main_block_impl_0 {
      struct __block_impl impl;//block基本信息结构体
      struct __main_block_desc_0* Desc;//描述结构体
      //这里可以看到两个函数内的局部变量,被声明到了block的结构体中
      const char *fmt;
      int val;
      //构造函数,其中构造函数也初始化了fmt和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;
      }
    };
    
    //block函数定义
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        const char *fmt = __cself->fmt; //拷贝block结构体里的fmt变量
        int val = __cself->val; //拷贝val变量
    
        printf(fmt, val);
        }
    
    int main(int argc, const char * argv[]) {
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d\n";
        //block对象blk的声明和定义,传入函数指针和描述结构体,以及fmt和val两个变量,这里的传值是拷贝
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
    
        val = 2;
    
        printf("val = %d\n", val);
        fmt = "These values were changed. val = %d\n";
        //调用blk函数
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    
        return 0;
    }
    

    通过上面的代码,可以发现,Block在函数内捕获的fmt和val两个自动变量均在Block结构体内重新声明了相同类型和名称的变量,并且在构造函数中通过拷贝的方式将值传递进来,也就是说,在定义blk对象的这条语句时,就已经将fmt和val的值传递进了Block结构体实例对象内,此时Block对象中保存的值是此时此刻fmt和val的值的拷贝,之后无论如何修改main函数中fmt和val的内容,都不会影响到block中的保存的fmt和val的副本。

    当然,若在函数内创建一个指针变量,例如在上边的代码添加

    int *p = &val;//将val的地址赋值给指针p
    

    此时,Block的结构体内也会申请一个int指针p,在构造函数的参数列表,传递的也是指针类型,所以在Block对象里修改p的地址对应的内容时,main函数中的指针p和val的值也会被修改。

    在Blocks中,截获自动变量的方法并没有实现对C语言数组的截获,使用指针可以解决该问题。
    const char text[] = "Hello";
    改成 const char *text = "Hello";

    修改Block外部变量的两种方式

    Block类型变量

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

    如果想修改一个外部变量,有两种方式可以实现。

    方法一:静态变量、静态全局变量、全局变量

    这三种变量,静态全局变量和全局变量的访问方式没有任何改变,Blocks可以直接使用。静态变量则是通过指针的方式传递。

    写一段包括这三种变量的代码

    int g_val = 1;//全局变量
    static int gs_val = 2;//全局静态变量
    
    int main(int argc, const char * argv[]) {
        //静态变量
        static int s_val = 3;
        //block代码块,截获这三种变量
        void (^blk)(void) = ^{
            g_val *= 1;
            gs_val *= 2;
            s_val *= 3;
        };
    

    转换后的代码如下:

    
    int g_val = 1;
    static int gs_val = 2;
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int *s_val;//对于静态变量s_val,使用s_val的指针对其进行访问
    
      //在构造函数里初始化s_val
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_s_val, int flags=0) : s_val(_s_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int *s_val = __cself->s_val; // bound by copy
    
            g_val *= 1;//对全局变量和全局静态变量的访问与转换前没有任何区别
            gs_val *= 2;//...
            (*s_val) *= 3;//使用指针的方式访问s_val
        }
    
    int main(int argc, const char * argv[]) {
        
        static int s_val = 3;
        
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &s_val));
        return 0;
    }
    

    对于静态变量s_val,使用指针对其访问,保存静态变量s_val的指针,传递给__main_block_impl_0结构体的构造函数并保存。这是超出作用域使用变量的最简单方法。

    而对于自动变量来说,如果使用指针的方式进行访问,当变量作用域结束的同时,原来的自动变量被废弃,此时Block中超过变量作用域的变量则不能通过指针访问,访问结果是未定义的。解决这个问题,则可以使用__block说明符。

    方法二:__block存储域类说明符(__block storage-class-specifier)

    C语言中有一下存储域类说明符:

    • typedef
    • extern
    • static
    • auto
    • register

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

    下面来写一段__block说明符的代码。

    int main(int argc, const char * argv[]) {
        //为val变量添加__block说明符
        __block int val = 10;
        //现在可以在Block代码块中为val正确赋值
        void (^blk)(void) = ^{
            val = 1;
        };
        return 0;
    }
    

    转换后的源代码如下:

    //新的结构体,用来保存用__block修饰的对象
    struct __Block_byref_val_0 {
      void *__isa;//结构体对象
    __Block_byref_val_0 *__forwarding;//指向自身
     int __flags;
     int __size;
     int val;//保存被__block修饰的val变量的值
    };
    //Block的结构体
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_val_0 *val; //保存了用__block修饰的变量的结构体
      //构造函数,使用_val->__forwarding来初始化val,这个设计的用意在后边说明
      __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;
      }
    };
    
    //Block代码块的内容,该例为对val进行赋值
    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(int argc, const char * argv[]) {
        //使用__Block_byref_val_0结构体来创建val对象,并对每一个值进行初始化
        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
        //定义Block对象
        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说明符修饰的自动变量,在源代码中实际上是一个__Block_byref_val_0的结构体实例,在__main_block_impl_0中以指针的形式持有,对该变量的修改实际上是通过结构体中的__forwarding指针指向的结构体实例,对val值进行修改。

    Block存储域

    在之前的代码中,可以看到在__main_block_impl_0结构体的构造函数中,将结构体中的isa变量赋值为_NSConcreteStackBlock,而与之类似的还有_NSConcreteGlobalBlock和_NSConcreteMallocBlock类。

    • _NSConcreteStackBlock:该类的对象设置在栈上
    • _NSConcreteGlobalBlock:该类对象设置在程序的数据区域(.data区)中
    • _NSConcreteMallocBlock:该类对象则设置在由malloc函数分配的内存块(堆)中

    其中,当全局变量区域定义Block代码块和Block语法的表达式不使用截获的自动变量时,Block即是_NSConcreteGlobalBlock对象。

    虽然通过clang转换的源代码通常是_NSConcreteStackBlock对象,但实现上却有不同。

    将Block从栈复制到堆上

    当Block语法记述的变量作用域结束时,栈上的Block和__block变量都会被废弃,此时通过Blocks提供的复制方法,将Block和__block变量从栈上复制到堆上,那么即使记述Block变量的作用域结束,堆上的Block还可以继续存在。

    复制到堆上的Block将_NSConcreteMallocBlock类对象写入Block用结构体实例的成员变量isa。

    首先看ARC有效时,自动生成的将Block从栈复制到堆上的代码。

    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);
        
        //objc_retainBlock函数实际上就是_Block_copy函数,该函数将栈上的Block复制到堆上,复制后,将堆上的地址作为指针赋值给变量tmp。
        //这里tmp是typedef int (^blk_t)(int),它的源代码实际上是typedef int (*blk_t)(int)函数指针,所以可以存储指针。
        tmp = objc_retainBlock(tmp);
        
        //将堆上的Block作为Objective-C对象,注册到autoreleasepool中,然后返回该对象。
        return objc_autoreleaseReturnValue(tmp);
    }
    

    上面是编译器自动生成的代码,而编译器在以下情况无法自动生成复制到堆上的代码

    • 向方法或函数的参数传递Block时

    以下方法或函数不用手动赋值

    • Cocoa框架的方法且方法名中含有usingBlock等时,如NSArray类的enumerateObjectsUsingBlock实例方法以及dispatch_async函数时,但在NSArray的initWithObjects方法中不能自动生成。
    • Grand Central Dispath的API

    程序员可以手动调用Block的copy方法。

    typedef int (^blk_t)(int);
    
    blk_t blk = ^(int count){return rate * count;};
    
    blk = [blk copy];
    

    对于在不同区域的Block调用copy方法所进行的动作如下表:

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

    不管Block配置在何处,用copy方法复制都不会引起任何问题。在不确定时调用copy方法即可。

    __block变量存储域与__forwarding指针

    前面提到,__block对象会被定义为__Block_byref_val_0的结构体实例,并成为__main_block_impl_0结构体实例的一个成员变量,所以当Block被从栈复制到堆上时,__block变量也会受到影响。总结如下表:

    __block变量的配置存储域 Block从栈复制到堆时的影响
    从栈复制到堆并被Block持有
    被Block持有

    在一个Block中使用__block变量,当该Block从栈复制到堆时,这些__block变量也全部从栈复制到堆。此时,Block持有__block变量。

    __forwarding指针

    在__block的结构体中,有一个__forwarding成员变量,在转换成C的源代码中可以看到,所有对__block变量的操作,均是通过__forwarding变量来操作,使用该变量,不管__block变量配置在栈上还是堆上,都能够正确地访问该变量。

    最初建__block变量时:

    • __forwarding指针指向的是该结构体实例自身。

    当Block从栈复制到堆上时, __block变量也从栈复制到堆上,此时:

    • 会将成员变量__forwarding的值替换为堆上的__block变量的结构体实例的地址。
    • Block语法外的__block变量的__forwarding指针也会指向复制到堆中的__block变量的结构体实例地址。

    整个过程如下图所示:

    复制__block变量.png

    通过该功能,无论是在Block语法中、Block语法外使用__block变量,还是__block变量配置在栈上或堆上,都可以顺利地访问同一个__block变量。

    截获对象

    在Block语法中调用语法外的对象,如下代码:

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

    执行结果为

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

    在执行最后三行代码的时候,array已经跑出了变量作用域,此时array对象被废弃,但结果运行正常。

    转换后的源代码如下,这里仅放了部分源代码:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      id array;//强持有
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    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};
    
    

    实际上在转换后的源代码的Block用结构体中,声明了一个id array成员对象,默认是__strong修饰,该成员对象以强持有的方式持有Block语法外的array变量,这就保证了在array变量被废弃时,Block的成员对象array仍然持有着NSMutableArray变量,所以代码可以正常运行。

    copy和dispose

    上面的源代码中新增了两个函数,__main_block_copy_0和__main_block_dispose_0,runtime通过这两个函数在运行过程中将Block从栈复制到堆上以及将堆上的Block废弃。

    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) 
    {
        _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src)
    {
        _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    

    这里边的_Block_object_assign函数相当于retain函数,将对象赋值在对象类型的结构体成员变量中。
    _Block_object_dispose相当于release函数,释放赋值在对象类型的结构体成员变量中的对象。

    copy和dispose函数的调用时机
    函数 调用时机
    copy函数 栈上的Block复制到堆时
    dispose函数 堆上的Block被废弃时
    Block从栈复制到堆的时机
    1. 调用Block的copy实例方法时
    2. Block作为函数返回值返回时
    3. 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
    4. 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中传递Block时

    在ARC有效时,上述的2与3情况,编译器会自动地将对象的Block作为参数并调用_Block_copy函数,这与调用Block的copy实例方法效果相同。在4的情况下,在调用的方法内部会对传递来的Block调用Block的copy实例方法或者_Block_copy函数。

    这里的第3条,在ARC有效的情况下,如执行这样的语句

    void (^blk)(void) = ^{
        ...
    };
    NSLog(@"%@", blk);
    

    上面定义的blk对象,实际上就是附有__strong修饰符的id类型,所以NSLog语句执行的结果就是blk是一个NSConcreteMallocBLock。

    BLOCK_FIELD_IS_OBJECT

    在上面两个函数中,可以注意到一个参数BLOCK_FIELD_IS_OBJECT,在前面使用__block的例子中生成的源代码里,也出现了类似的参数BLOCK_FIELD_IS_BYREF。

    • BLOCK_FIELD_IS_OBJECT --> 对象
    • BLOCK_FIELD_IS_BYREF --> __block变量

    编译器通过该参数区分copy函数和dispose函数的对象类型是对象还是__block变量。

    这里通过runtime的源代码中可以查看到枚举的全部定义

    enum {
        // see function implementation for a more complete description of these fields and combinations
        BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
        BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
        BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
        BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
        BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
    };
    

    在ARC有效的情况下,编译器也会有不自动调用copy的情况,除了以下几种情况外,推荐手动调用copy实例方法。

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

    __block变量和对象

    __block说明符可指定任何类型的自动变量。如下例子所示:

    __block id __strong obj = [[NSObject alloc] init];
    

    转换后的源代码:

    struct __Block_byref_obj_0 {
      void *__isa;
    __Block_byref_obj_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     id obj;
    };
    
    static void __Block_byref_id_object_copy_131(void *dst, void *src) {
        //dst的地址加40,包括4个指针(isa,__forwarding,两个函数指针)32个字节,两个int为8个字节,将指针移动到obj的起始地址
        //131是上面枚举中BLOCK_FIELD_IS_OBJECT和BLOCK_BYREF_CALLER取或的结果
        _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    }
    static void __Block_byref_id_object_dispose_131(void *src) {
        _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    }
    
    int main(int argc, const char * argv[]) {
        __attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {
            (void*)0,
            (__Block_byref_obj_0 *)&obj, 
            33554432, 
            sizeof(__Block_byref_obj_0), 
            __Block_byref_id_object_copy_131, 
            __Block_byref_id_object_dispose_131, 
            ((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修饰符的id类型或对象类型自动变量的情形下会发生与在Block中使用附有__strong修饰符的id类型或对象类型自动变量的情况下相同的过程。当__block变量从栈复制到堆时,使用_Block_object_assign函数,废弃时使用_Block_object_dispose函数。

    使用__weak说明符的__block变量

    当使用__weak说明符修饰的变量在作用域结束后,即是Block代码块中使用了该变量,也会被释放、废弃,变量会变成nil。

    __unsafe_retained说明符

    __unsafe_retained说明符表明不归编译器管理对象,被它修饰的变量不会像__strong或__weak修饰符那样处理,注意不要通过悬垂指针访问已被废弃的对象。

    不能同时使用__autoreleasing和__block

    Block循环引用

    这个很容易理解,在类中的Block语法中直接调用self或者调用类的成员对象,会导致Block强持有类,但同时类也强持有Block对象,导致循环引用。

    解决方法1
    在Block语法外声明一个self的弱引用,该方法只在ARC有效的情况下游泳

    __weak typeof(self) weakSelf = self;
    

    上述方法让Block以weak的方式持有self,这样就不会引起循环引用,导致内存泄漏。
    而这样会引发另一个问题,Block中的代码不是立即执行,在执行的时候可能该weak指针已经被销毁了,所以self会变成nil,这样需要在Block语法内部再添加一局

    __strong typeof(weakSelf) strongSelf = weakSelf;
    

    在Block语法内部使用strongSelf来获取self的相关属性和方法,当外部self被release后,strongSelf在block的语法局部还持有该self,当Block语法执行完毕后,strongSelf的生命周期结束被release,此时self的引用计数为0,对象被销毁。

    解决方法2
    使用__block,在成员方法内

    __block id tmp = self;
    blk_ = ^ {
        NSLog(@"self = %@", tmp);
        tmp = nil;
    };
    

    但该方法有局限性,必须保证Block对象的代码执行一次。

    使用__block变量的优点如下:

    • 通过__block变量可控制对象的持有期间
    • 在不能使用__weak修饰符的环境中不使用__unsafe_unretained修饰符即可

    copy/release

    在ARC无效时,需要手动将Block从栈复制到堆上,同样需要手动释放Block。

    推荐使用copy实例方法代替retain实例方法。
    使用release方法释放Block。

    同样可以使用C语言的Block_copy函数和Block_release函数,它与copy和release方法效果相同。

    ARC无效时的__block

    在ARC无效时,__block说明符被用来避免Block中的循环引用。当Block从栈复制到堆时,若Block使用的变量为附有__block说明符的id类型或对象类型的自动变量,不会被retain(不会被retain则意味着不会导致循环引用),而若没有被__block说明符修饰,则会被retain。

    相关文章

      网友评论

        本文标题:Blocks的实现

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