美文网首页iOS学习开发
关于解决block循环引用的一点思考

关于解决block循环引用的一点思考

作者: 牛顿爱编程 | 来源:发表于2018-03-09 10:28 被阅读9次

    导语:使用weakSelf,strongSelf可以解决循环引用原理分析


    在使用block过程中经常会出现循环引用的情况:

    比如下面的代码:

      self.block = ^() {
              [self doSomething];
        };
    

    因为block作为当前对象的一个属性,又强引用了该对象,导致循环引用。

    解决方法一般:

     __weak __typeof(self) weakSelf = self;
        self.block = ^() {
            __strong __typeof(weakSelf) strongSelf = weakSelf;
            [strongSelf doSomething];
        };
    

    但是该解决方式在block中又强引用了weakSelf,为什么没有导致循环引用呢?

    之前也一直比较疑惑,后来分析了下block的源码,终于搞清楚了。

    首先,我们看一下什么是block。

    (1)新建一个文件,定义一个block并执行:

    #include <stdio.h>
    
    int main(){
        void (^blk)(void) = ^(){printf("Hello world!");};
        blk();
        return 0;
    }
    

    对于这个简单的block文件使用 clang -rewrite-objc hello.c 语句可以得到它对应的cpp文件,主要代码有:

    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) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("Hello world!");}
    
    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_impl结构体中有四个变量:其中isa表明block也有runntime的特性,isa是指向Class的指针;FuncPtr是函数指针,指向block实际执行的函数。

    __main_block_impl_0结构体有两个变量:一个是__block_impl变量;一个是__main_block_desc_0变量,是对__main_block_impl_0的描述。

    __main_block_func_0是一个方法,也就是调用block时执行的函数。

    再看main()函数,可以看到block实际被转换成一个指向__main_block_impl_0结构体的指针,block()执行过程也就是blk->FuncPtr()的执行过程。
    (2)修改一下文件中的代码:

    #include <stdio.h>
    
    int main(){
        int i = 0;
        void (^blk)(void) = ^(){printf("i is %d", i);};
        i++;
        blk();
        return 0;
    }
    

    转换成cpp文件:

    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;
      int i;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int i = __cself->i; // bound by copy
    printf("i is %d", i);}
    
    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 i = 0;
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
        i++;
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
        return 0;
    }
    

    和之前的代码比较可以发现,在__main_block_impl_0结构体中多了一个变量i,并且在创建block的时候i被赋值,注意int i = __cself->i; // bound by copy 所以block创建之后再修改i的值对于block的执行结果没有影响。
    (3)继续修改:

    #include <stdio.h>
    
    int main(){
        int __block i = 0;
        void (^blk)(void) = ^(){printf("i is %d", i);};
        i++;
        blk();
        return 0;
    }
    

    cpp文件:

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    struct __Block_byref_i_0 {
      void *__isa;
    __Block_byref_i_0 *__forwarding;
     int __flags;
     int __size;
     int i;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_i_0 *i; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref
    printf("i is %d", (i->__forwarding->i));}
    
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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(){
        __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
        (i.__forwarding->i)++;
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
        return 0;
    }
    

    该block中包含可以修改的变量,情况相对比较复杂。只看变化比较明显的地方,此时i是通过__Block_byref_i_0 *i; // by ref 引用到外面的i值,也就是外面i值的改变是可以改变block中的i值。更深入的分析由于水平有限暂时不展开了。

    所以:

    self.block = ^() {
              [self doSomething];
        };
    

    相关文章

      网友评论

        本文标题:关于解决block循环引用的一点思考

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