美文网首页
Blocks实现原理讲解

Blocks实现原理讲解

作者: 凌巅 | 来源:发表于2016-06-03 12:40 被阅读156次

    全乎的Blocks讲解

    一句话概括Blocks:<strong>带有自动变量(局部变量)的匿名函数</strong>

    Blocks模式

    Block语法

    Block的书写形式:<strong>^ 返回类型 参数列表 表达式</strong>

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

    我们可以看到Block的语法和C语言函数相比有两点不同

    1. 没有函数名
    2. 带有“^”
      省略形式:
      1、返回类型。省略返回类型时,如果表达式中有return语句就使用该返回类型,如果表达式没有return语句就是用void类型。表达式中含有多个return语句时,所有return的返回类型必须相同。
      2、参数列表。如果使用参数,参数列表也可以忽略。
      综上,最简单的Block形式如下:
        ^{
            printf("Blocks\n");
        }
    

    Blocks变量

    (一)Block类型变量与一般的C语言变量完全相同,可作为一下用途使用:
    1、自动变量
    2、函数参数
    3、静态变量
    4、静态全局变量
    5、全局变量
    当在函数和返回值中使用Block类型变量是,记述方式极为复杂。此时,我们可以像使用函数指针类型那样,使用typedef来解决问题。

        typedef int (^blk_t)(int);
        void func(int (^blk)(int))//原来的记述方式
        void func(blk_t blk)  //现在的记述方式
          ·
        int (^func())//原来的记述方式
        blk_t func() //现在的记述方式
    

    (二)Block中变量值得获取
    Block表达式截获所使用的自动变量的值,即保存自动变量的瞬间值。
    (三)__block说明符
    默认情况下,执行Block语法时,截获的是自动变量的瞬间值,截获保存之后就不能改写这个值,如果尝试修改这个值,编译器会报编译错误。
    如果想在Block语法的表达式中将值赋给Block语法外声明的自动变量,修改在该自动变量上附加__block说明符。

    Blocks底层的实现

    Block是“带有自动变量的匿名函数”,实际上Block是作为极普通的c语言源代码来处理的。我们先来看一下个最简单的block语法:

        int main()
        {
            void (^blk)(void) = ^{printf("Block\n");};
            blk();
            return 0;
        }
    

    我们使用clang将objc代码转换为c++的源代码。说是c++,其实也仅是使用了struct结构,本质还是c代码。

    //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) {
        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;
    }
    

    objc中短短的几行代码,转换为c++代码后,竟然增加了这么多。我们从转换后的代码中选取出最重要的这几行代码。下面我们就来分析一下转换的代码,看看block到底是怎么通过c++代码来实现的。
    很简单的,第一眼我们就看到转换后的代码中有一行printf("block\n");,没错,这就是objc代码中的block块内容。发现原来的Block函数,转换成了一个c++函数:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
                printf("block\n");
        }
    

    函数的名字是根据Block语法所属的函数名和该Block语法在该函数出现的顺序值来给Block函数命名。
    此外,我们发现转换的后函数有一个入参__cself,这个参数就相当于c++实例方法中指向实例自身的变量this或者objc实例方法中指向对象自身的变量self,即参数__cself为指向Block值得变量。我们先来看看这个参数的声明:

    struct __main_block_impl_0 * __cself
    

    __cself__main_block_impl_0结构体的指针,该结构声明如下:

    //去除构造函数 
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
    };
    

    __main_block_impl_0结构体有两个成员变量,一个__block_impl 结构体impl,一个 __main_block_desc_0结构体指针Desc。一个一个的来看,
    先看一下 __block_impl 这个结构体

     struct __block_impl {
            void *isa;  //如同对象类型的Class isa,将Block作为Objc对象是,关于该对象类的信息放置于isa指向的对象中
            int Flags;  //某些标志
            int Reserved; //保留区域
            void *FuncPtr; //函数指针
    }
    

    我们来分一下这个结构体中的四个参数:
    1、isa,我们首先要认识到Blocks在objc也是对象,在Objective-C中使用id这一类型来存储对象。

    typedef struct objc_object {
        Class isa;
    } *id;
    

    objc_object是表示一个类的实例的结构体,id为objc_object结构体的指针类型。
    该结构体只有一个字段,即指向 其类的isa指针。这样,当我们向一个Objective-C对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。Runtime库会在类的方法列表及父类的方法列表中去寻找与消息对象的selector指向的方法。找到后即运行这个方法。当创建一个特定类的实例对象时,分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据。
    我们再来看看Class:

    typedef struct objc_class *Class;
    

    Class为objc_class结构体的指针类型。objc_class结构体的定义如下:

    struct objc_class {
        Class isa; //在Objective-c中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,指向metaClass(元类)
        ·
        ·
        Class super_class; //指向该类的父类,如果该类已经是最顶层的根类,则super_class为NULL
    }
    

    在这里我们顺便提一下元类的概念。上面介绍objc_class结构是,提到,所有的类自身也是一个对象,我们可以向这个对象发送消息。如:

    NSArray *array = [NSArray array];
    

    在这个例子中,+array消息发送给了NSArray类,而这个NAArray类也是一个对象。既然是对象,那么他也是一个Objc_object指针,他包含一个指向其类的一个isa指针。那么这些就有一个问题了,这个isa指针指向什么呢?为了调用+array方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念:<strong>meta-class是一个类对象的类</strong>.
    当我们向一个对象发消息时,runtime会在这个对象所属的这个类的方法列表中查找相应方法,而向一个类发送消息是,会在这个类的meta-calss元类的方法列表中查找。
    ps:meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同
    实例-类-元类的关系如下图:


    </br>
    回到正文,__main_block_impl_0结构就相当于objc_object结构体,其内部的isa指针目前一般初始化为isa = &_NSConcreterStackBlock;,即该Block类的信息放在了_NSConcreterStackBlock中。
    2、Flags 标志位,默认设为0
    3、Reserved 保留区
    4、FuncPtr 函数指针,将Block转换后的函数指针赋值给FuncPtr,供以后调用。

    接下来我们来看一下这个__main_block_desc_0结构体,其声明如下:

    static struct __main_block_desc_0 {
      size_t reserved;  //保留区域
      size_t Block_size; //Block的大小
    };
    

    说了这么多,我们来看一下objc是怎么把Block转换成c++函数调用的。
    首先我们构造过程:

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

    乍一看,这个函数比较复杂,转换较多,我们分开来看:

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

    整个过程就是将__mian_block_impl_0结构体类型的自我变量,即栈上生成的__mian_block_impl_0结构体实例的指针,赋值给__mian_block_impl_0结构体指针类型的变量blk。对应最初源代码:

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

    下面就来看看__mian_block_impl_0结构体实例的构造函数:

    __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)
    

    第一个参数是由Block语法转换后的C语言函数指针,第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针。__main_block_desc_0的初始化如下:

    static struct __main_block_desc_0 __main_block_desc_0_DATA = { 
            0, 
            sizeof(struct __main_block_impl_0) //__main_block_impl_0结构体实例的大小
        };
    

    __mian_block_impl_0结构体实例具体是如何初始化的呢,我们将__mian_block_impl_0结构体中的成员变量展开,如下:

    struct __mian_block_impl_0 {
        void *isa; 
        int Flags;
        int Reserved;
        void *FuncPtr;
        struct __main_block_desc_0 * Desc;
    }
    

    初始化如下:

    isa = &_NSConcreterStackBlock; 
    Flags = 0;
    Reserved = 0;
    FuncPtr = __main_block_func_0;
    Desc = &__main_block_desc_0_DATA;
    

    再来看一下调用过程:

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

    去掉转换部分:

    (*blk->FuncPtr)(blk)
    

    就是简单地使用函数指针调用函数。Block语法转换的__main_block_func_0函数指针被赋值给成员变量FuncPtr。此外,我们也发现了__main_block_func_0函数的参数__cself指向Block值。

    Block截获自动变量值

    上一节我们讲解了Block语法的底层实现,这一节主要讲解如何截取自动变量的值,前面曾提到过,Block获取的是自动变量的瞬间值。和前面一样,我们还使用clang对源代码进行转换。
    源代码如下:

    int main()
    {
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d\n";
        void (^blk)(void) = ^{printf(fmt,val);};
        blk();
        return 0;
    }
    

    转换后的代码:

    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;
      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 dmy = 256;
        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));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
        return 0;
    }
    

    与上一节的代码作比较,我们注意到,在__main_block_impl_0结构体中增加两个变量const char *fmtint val,类型与自动变量的类型完全相同。而且,还发现Block语法表达式中没有使用的自动变量不会被追加。即Blocks的自动变量截获只针对Block中使用的自动变量。
    接下来我们再看一下构造函数:

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

    可以发现,执行Block语法时,使用自动变量fmt和val来初始化__main_block_impl_0结构体实例。
    再来看一下执行函数:

    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);
    }
    

    再转换后的函数中,使用的是自动变量保存在__main_block_impl_0结构体实例中的值。由此也说明了,Block获取的是自动变量的瞬间值。

    __block说明符

    前面我们说到,Block获取变量时是保存自动变量的瞬间值,在Block内部如果想要改变自动变量的值,编译器会报编译错误。那么如何才能在Block内部实现修改保存自动变量的值呢,在没用使用__block说明符之前,我们可以根据C语言的特性来实现:
    1、静态变量
    2、静态全局变量
    3、全局变量
    我们来看看这段源代码:

    int global_val = 1;
    static int static_global_val = 2;
    int main()
    {
        static int static_val = 3;
        void (^blk)(void) = ^{
            global_val *= 1;
            static_global_val *= 2;
            static_val *= 3;
        };
        return 0;
    }
    

    改源代码里面使用Block改写了静态变量static_val、静态全局变量static_global_val和全局变量global_val。转换后的代码如下:

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    int global_val = 1;
    static int static_global_val = 2;
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int *static_val; //静态变量static_val的指针
      __main_block_impl_0(void *fp, struct __main_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 __main_block_func_0(struct __main_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 __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()
    {
     static int static_val = 3;
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));  //静态变量static_val的指针传递给__main_block_impl_0结构体的构造函数
        return 0;
    }
    

    我们看到,对于静态全局变量static_global_val和全局变量global_val,在转换后的函数中直接使用。但是对于静态变量static_val却进行了转换,使用静态变量static_val的指针对其进行访问。将静态变量static_val的指针传递给__main_block_impl_0结构体的构造函数并保存。这里我们就要问了,为什么自动变量不保存它的指针呢。
    在实际使用中,自动变量分配在栈上,在由Block语法生成的Block上,经常情况下Block会在超过其变量作用域的时刻执行,当变量作用域结束时,自动变量就废弃了,通过自动变量的指针去访问已废弃的自动变量,会发生错误。
    </br>
    Objective-C提供了 __block说明符来解决这个问题。 __block说明符用来指定Block中想变更的自动变量。且看下面的源代码:

    int main()
    {
        __block int val = 3;  //__block修改val自动变量
        void (^blk)(void) = ^{
            val *= 1;   //修改自动变量的值
        };
        return 0;
    }
    

    转换后的代码如下:

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    ·
    ·
    //新增的结构体
    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), 3};
        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 变量val变为了一个结构体实例

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

    该结构体的初始化过程如下:

    __Block_byref_val_0 val = {
        0, //__isa
        &val, //__forwarding指向自己
        0,  //__flags = 0
        sizeof(__Block_byref_val_0), //__size 为自身__Block_byref_val_0的大小
        10,  //自动变量的值
    }
    

    那么如何给__block变量赋值呢?

    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;
    }
    

    通过__main_block_impl_0结构体中__Block_byref_val_0结构体实例的指针找到自动变量的结构体实例,然后通过自动变量结构体的成员变量__forwarding__forwarding持有指向该实例自身的指针)访问成员变量val(原变量).


    我们为什么是通过成员变量__forwarding而不是直接去访问结构体中我们需要修改的变量呢? __forwarding被设计出来的原因又是什么呢?

    Block存储域

    我们知道,Block语法块转换为Block结构体类型(函数)的自动变量,__block变量转换为__block变量的结构体类型的自动变量。而这两种结构体类型的自动变量,存在于栈上。
    通过前面的介绍,我们知道Block也是个Objective-C对象,将Block当作Objective-C对象来看待时,该Block的类为_NSConcreteStackBlock,之前我们提过Block类中有一个isa指针,在初始化时指向_NSConcreteStackBlock,其实除了这个之外还有两个类似的类:

    • __NSConcreteStackBlock
    • __NSConcreteGlobalBlock
    • __NSConcreteMallocBlock

    通过名字我们就可以知道__NSConcreteStackBlock表示类的对象Block设置在栈上。
    __NSConcreteGlobalBlock 表示类的对象Block设置在程序的数据区(.data)中
    __NSConcreteMallocBlock 表示类的对象Block设置由malloc函数分配的内存块(堆)中
    但是到目前为止,Block使用的都是__NSConcreteStackBlock 类,都是设置在栈上。但是也不全是这样,在记述全局变量的地方使用Block语法时,生成的Block为__NSConcreteGlobalBlock。此外,如果Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区(使用__NSConcreteGlobalBlock类)。
    那么什么时候使用__NSConcreteMallocBlock这个类呢。这里我们就来顺便解决一下上面遗留的问题:

    • Block超出变量作用域可存在的原因
    • __block变量用结构体成员变量__forwarding存在的原因。

    我们知道,当一个变量设置在全局区时,从变量的作用于外也可以通过指针安全的使用。但是当设置在栈上时,当作用域结束时,这个Block就被废弃了。___block变量也是同样的情况。
    Blocks提供了将Block和__block 变量从栈上复制到堆上的方法来解决这个问题。将配置在栈上的Block复制到堆上,这样即使Block语法记述的变量作用域结束,堆上的Block还可以继续存在。 当Block从栈上复制到堆上时,Block将结构体实例的isa设置成__NSConcreteMallocBlock
    我们再来看看__forwarding成员变量,我们知道,有时候__block变量配置在堆上,也可以访问栈上的__block变量。在这种情况下,只要栈上的结构体实例成员变量__forwarding指向堆上的结构体实例,那么不管是从栈上的__block变量还是从堆上的__block变量都能正确的访问。
    那么什么时候栈上的Block会复制到堆上呢?

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

    __block变量存储域

    上一节介绍了Block的存储,那么__block变量又是如何处理的呢,当Block从栈复制到堆时,使用的所有__block变量也全部被从栈复制到堆。此时,Block持有__block变量。
    在多个Block中使用__block变量时,因为最先会将所有的Block配置在栈上,所以__block变量也会配置在栈上。在任何一个Block从栈复制到堆时,__block变量也会一并从栈复制到堆并被该Block所持有。当剩下的Block从栈复制到堆时,被复制的Block持有__block变量,并增加__block变量的引用计数。
    如果配置在堆上的Block被废弃,那么它所使用的__block变量也就被废弃。
    之前我们提到过一句话:“不管__block变量配置在栈上还是在堆上,都能够访问该变量”。这也就是__forwarding成员变量存在的原因。
    这里引用一下上面出现过的代码:

     (val->__forwarding->val) *= 1;
    

    在使用__block变量时,通过__forwarding成员变量访问val成员(原变量)。当__block变量从栈上复制到堆上时,会将__forwarding成员变量的值替换为复制目标堆上的__block变量用结构体实例的地址。
    ps: 栈上和堆上同时保持__block变量实例,但是访问和修改值则是在堆上。看张图吧:

    截获Block对象

    我们看一下源代码:

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

    代码中使用copy函数将Block从栈上复制到堆上,而使用的array这个自动变量也一并复制到堆上。(当ARC有效是,id类型以及对象类型变量必定附加所有权标识符,缺省为附有__strong 修饰符的变量)。Objective-C的运行时库能够准确把握Block从栈上复制到堆上以及Block被废弃的时机,能够在恰当的时机进行初始化和废弃。为此需要使用在__main_block_desc_0结构体中增加的成员变量copy和dispose,即作为指针赋值给该成员变量的__main_block_copy_0__main_block_dispose_0 函数。我们来依次看看这两个函数的具体实现:

    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*/);}
    

    _Block_object_assign 函数调用,相当于retain实例方法的函数。将对象赋值在对象类型的结构体成员变量中。有retain方法,肯定有release方法。__main_block_dispose_0函数调用__main_block_dispose_0函数释放赋值在Block中的结构体成员变量arrar中的对象。

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

    __main_block_dispose_0 函数相当于release实例方法函数,释放赋值在对象类型的结构体成员变量中的对象。
    有了这种构造,通过使用附有__strong 修饰符的自动变量,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->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};
    

    和Block结构体的部分基本相同,不同支出在于__main_block_copy_0__main_block_dispose_0函数最后的参数不一样。BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF区分对象类型是对象还是__block变量。
    由此可知。Block中使用的赋值为附有__strong修饰符的自动变量的对象和复制到堆上的__block变量由于被堆上的Block所持有,因而可超出其变量作用域而存在。

    Block循环引用

    如果在Block中使用附有__strong修饰符的自动变量,那么当Block从栈复制到堆上时,该对象为Block所持有,很容易引起循环引用。这种情况经常出现在一个类型对象中,有一个Block类型的成员变量,而这个Block中又引用了self变量,这就会造成一个循环引用。Block持有self,self持有Block。

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

    若由于Block引发了循环引用,根据Block的用途选择使用__block变量、__weak修饰符或__unsafe_unretained修饰符来避免循环引用。

    相关文章

      网友评论

          本文标题:Blocks实现原理讲解

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