美文网首页iOS底层原理文集
29.iOS底层学习之block底层原理

29.iOS底层学习之block底层原理

作者: 牛牛大王奥利给 | 来源:发表于2022-01-28 15:25 被阅读0次

    本章提纲
    1、Block对变量的捕获
    2、_ _block做了什么?
    3、Stack类型的Block是如何变成Malloc类型的?
    4、Block的数据结构以及源码分析
    5、Block的释放

    1.Block对变量的捕获

    Block对变量的捕获我们主要来研究四种:一个是对值类型的捕获;加了__block之后的值类型的捕获;对指针类型的捕获;对加了__block的指针类型的捕获。

    • 对值类型的捕获
      首先实现一个最基本的block,在块中使用局部变量a
    int main(){
        int a = 18;
        void(^block)(void) = ^{
            printf("lucky - %d",a);
        };
         block();
        return 0;
    }
    

    我们通过xcrun命令,编译一下这个文件,看看c++的实现。

    block.png
    可以看到main函数中的block变成了对应的__main_block_impl_0,并且有三个参数,其中a也传了进来。
    block();的调用转化成了block->FuncPtr
    进一步我们先找到__main_block_impl_0的定义:
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int a;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    通过这个底层的代码,可以看出来Block的本质是struct结构体。所以block块的生成实际上是调用它的构造函数。并且在block的内部生成了a的成员变量,通过函数__main_block_impl_0参数的方式传了进去进行赋值。
    __main_block_func_0参数1

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        int a = __cself->a; // bound by copy
        printf("lucky - %d",a);
    }
    

    通过这个参数一的实现,可以看到a在这个块中又被copy了一份儿,所以这个内部的块儿中的a和外部的a已经不是一个地址了,所以外部修改a不会影响到内部a的值了!

    __main_block_desc_0结构体指针类型。

    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)};
    
    • 加了__block的值类型捕获
      接下来我们改一下代码,看看a加了__block之后生成的代码变化。
    int main(){
        __block int a = 18;
        void(^block)(void) = ^{
            a++;
            printf("lucky - %d",a);
        };
         block();
        return 0;
    }
    

    代码做如上修改,结果编译结果变成如下:

    image.png
    对比原来的没有添加__block的情况,a在传入__main_block_impl_0中变成了__Block_byref_a_0类型的a,是取a的地址。
    __Block_byref_a_0的定义如下:
    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     int a;
    };
    

    它也是个结构体。进一步来看block编译后的变化:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_a_0 *a; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref
    
            (a->__forwarding->a)++;
            printf("lucky - %d",(a->__forwarding->a));
        }
    

    __main_block_impl_0内部的a此时变成了__Block_byref_a_0 *a;,不再是int a了,也就是说,加了__block修饰这个a之后,原来的a被block捕获是a的地址了,并且通过__Block_byref_a_0来记录。
    再来看参数__main_block_func_0的实现,内部取到a,是通过a__forwarding再取到a的值进行处理,此时都是地址操作,所以他们最终指向的内存空间是一块儿,就可以在块内对外部的变量进行操作了。

    • 对指针类型的捕获
      再修改代码,把a改成NSObject然后看看编译之后的结果。
    对象捕获.png

    所以block捕获对象也是指针传递,进入内部是可以对值进行修改的,可以看到编译后的底层也是传进去的是指针。

    • 加了__block的指针类型的捕获
      image.png
      和值类型一样,也是对应生成了一个__Block_byref_p1_0,同样也是指针传递。

    2._ _block做了什么?

    所以根据以上的例子,我们可以总结出来__block修饰值,或者对象后,相应的会生成一个__Block_byref_x_0的结构体,而x(a或者是p1)会作为一个成员变量在这个结构体中,读取的时候会通过x->__forwarding->x去获取值,是通过地址指向的方式拿到对应的值。

    所以值类型前边加了__block,就相当于生成了一个结构体类型,由原来的值传递,变为相应的地址传递!由深拷贝变成了浅拷贝

    • 深拷贝与浅拷贝
      用一个比喻来简单解释一下深拷贝和浅拷贝的区别,虽然不够准确,但是便于理解。
      比如你有一辆宝马x3小汽车,然后你有一把车钥匙。浅拷贝的意思就是,别人来了想管你借车,然后你又搞了一把钥匙给他,这时候你们两个访问的是同一辆车,只要其中一个人在车里做了手脚,比如在里边吃薯片把车里弄的乱七八糟的,等另外一个人进来也会发现里边乱七八糟的,这车坏了受损了,你们两个就谁也操作不了了。

      而深拷贝就是别人想管你借车,你又去4s店搞了一辆一模一样的车给它,两把钥匙,两辆车,你朋友的车里乱七八糟的不会影响到你开的车,就是两个空间,相互不受影响。但是这样做的劣势也很明显就是浪费了资源(内存资源)。除非非常必要的时候,否则系统默认会节省下这部分的资源。所以常见的编译器拷贝大多是浅拷贝。

      iOS中自定义对象需要进行深拷贝的时候要去自己实现NSCopying协议或者NSMutableCopying,重写方法。

    3.Stack类型的Block是如何变成Malloc类型的?

    探究这个问题,我们通过运行代码,下符号断点的方式来探究。我们打开符号断点,并在调用block之前打一个断点,block的调用的地方打一个断点。我们看一下截图:

    image.png
    根据汇编可以看到,后面调用block(),调用的是objc_retainBlock
    我们进一步下符号断点objc_retainBlock来看下它的出处。 image.png

    再次运行:


    objc_retainBlock

    看到objc_retainBlock是在libobjc.A.dylib库中的_Block_copy方法。所以这一段实际上执行的是_Block_copy方法。我们再下符号断点_Block_copy,切换成真机调试,来看一下寄存器中的block信息,以及_Block_copy的出处。

    stackBlock
    刚进入_Block_copy方法时,block是stack类型。我们在_Block_copy返回处再打个断点调试。
    mallocBlock
    也就是说block经过方法_Block_copy就从stack变为了malloc类型。
    下面我们就从源码入手来看看_Block_copy的具体操作,从上面的符号断点可以看到_Block_copy是来自libsystem_blocks.dylib,但是这个libsystem并没有开源,我们可以用libclosure替代看一下实现。

    4.Block的数据结构以及源码分析

    我们打开libclosure,搜索_Block_copy(添加了注释,省略了预处理部分):

    _Block_copy

    latching_incr_int的实现

    latching_incr_int
    Block的数据结构

    Block的类型为Block_layout,是一个结构体:

    struct Block_layout {
        void * __ptrauth_objc_isa_pointer isa;
        volatile int32_t flags; // contains ref count
        int32_t reserved;
        BlockInvokeFunction invoke;
        struct Block_descriptor_1 *descriptor;
        // imported variables
    };
    

    1、结构体的成员有一个isa,说明也是一个对象;
    2、flag标志位里边包括引用计数,记录block的各种状态,枚举如下(添加少量注释):

    // Values for Block_layout->flags to describe block objects 一共占32位 从第0位开始
    enum {
        BLOCK_DEALLOCATING =      (0x0001),  // runtime
        BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime 0x 1111 1111 1111 1110 2^16-2
        BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler 2^21
    
    #if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
        BLOCK_SMALL_DESCRIPTOR =  (1 << 22), // compiler2^22
    #endif
    
        BLOCK_IS_NOESCAPE =       (1 << 23), // compiler2^23
        BLOCK_NEEDS_FREE =        (1 << 24), // runtime 2^24
        BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler 2^25
        BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code 2^26
        BLOCK_IS_GC =             (1 << 27), // runtime 2^27
        BLOCK_IS_GLOBAL =         (1 << 28), // compiler 2^28
        BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE 2^29
        BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler 2^30
        BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler 2^31
    };
    
    

    标志位解析:

    • BLOCK_DEALLOCATING,第零位,释放标记,通常与BLOCK_NEEDS_FREE做位与操作,告知该block可以释放。
    • BLOCK_REFCOUNT_MASK,引用计数最大值,第一到第十五位表示引用计数值,最大是BLOCK_REFCOUNT_MASK。
    • BLOCK_IS_NOESCAPE,第二十三位,是不是可以进行引用计数加减操作。
    • BLOCK_NEEDS_FREE,第二十四位,低16位是否有效的标志,程序根据它来决定是否增加或者减少引用计数位的值
    • BLOCK_HAS_COPY_DISPOSE,第二十五位,是否拥有拷贝辅助函数
    • BLOCK_HAS_CTOR,第二十六位,是否拥有Block的C++析构函数。
    • BLOCK_IS_GC,第二十七位,是否开启垃圾回收机制,macOS专有。
    • BLOCK_IS_GLOBAL,第二十八位,是否是全局Block。
    • BLOCK_USE_STRET,第二十九位,与BLOCK_HAS_SIGNATURE相对,判断是否当前Block拥有一个签名,用于runtime时动态调用。
    • BLOCK_HAS_SIGNATURE,第三十位,是否有签名。

    前边我们通过lldb调试Block时打印出了Block的签名为:v8@?0,它的含义是:
    v代表返回值类型是void。
    8代表占用的空间。
    @?代表block类型。
    0代表从0号位置开始。

    3、reserved预留字段。
    4、invoke函数指针,指向Block实现的调用地址。
    5、descriptor,附加信息。

    源码解析
    • _Block_copy分为三个大的部分,对三种block类型的区分然后分别去进行处理。
      1、aBlock->flags & BLOCK_NEEDS_FREE对应的是堆block,我们如果光从block的flag去看的话可能发现不了这个是Malloc类型的block。

      这个部分我们要结合3 stackblock去看,stackBlock直接给了注释,然后根据对stack的操作可以发现,当stack完成了copy后,isa指向了malloc类型,然后标志位置为了BLOCK_NEEDS_FREE,由此可以推断,这个flag = BLOCK_NEEDS_FREE时为堆Block。

      当为MallocBlock类型时,调用了latching_incr_int方法,然后直接返回block,所以关键来看latching_incr_int方法。

      • latching_incr_int方法解析
        内部有两个判断,如果此时标志位为BLOCK_DEALLOCATING,返回BLOCK_REFCOUNT_MASK。
        如果OSAtomicCompareAndSwapInt为真,返回old_value+2。这里+2对应+二进制10,引用计数占标志位的1到15位,所以是从第一位开始加,第0位是表示BLOCK_DEALLOCATING这个状态。

    所以我们可以简单总结出,当为堆Block时,引用计数+1,然后返回block。

    2、aBlock->flags & BLOCK_IS_GLOBAL 当标志位为BLOCK_IS_GLOBAL,也就是为Global类型的Block时,直接返回Block什么也不做。

    3、// Its a stack block.,stack类型的Block的处理。

    • malloc和block同样大小的空间
    • 拷贝block的内容到空间中去
    • 先清空标志位全部为0
    • 设置引用计数为1,设置第二十五位为1也就是BLOCK_NEEDS_FREE为1
    • isa指向_NSConcreteMallocBlock
    • 返回新创建的block。

    4、_Block_call_copy_helper的实现

    它的内部通过源码了解,主要调用的方法是_Block_get_copy_function,所以下面主要是_Block_get_copy_function的源码解释

    _Block_get_copy_function

    而方法_Block_get_copy_function中的_Block_get_descriptor的实现主要就是Block_layout结构体中Block_descriptor_1的获取。

    static inline void *
    _Block_get_descriptor(struct Block_layout *aBlock)
    {
        void *descriptor;
    //......
        //获取Block_layout中的descriptor的指针
        descriptor = (void *)aBlock->descriptor;
    //......
        return descriptor;
    }
    

    我们进一步查看Block_descriptor_1,然后在Block_descriptor_1的附近发现了2和3。
    Block_descriptor_1、2、3的实现

    Block_descriptor_1、2、3

    Block_descriptor_2Block_descriptor_3的获取

    Block_descriptor_2和Block_descriptor_3

    通过以上这些代码可以推断出_Block_call_copy_helper的过程。

    • _Block_call_copy_helper的主要实现方法是_Block_get_copy_function
    • _Block_get_copy_function的操作是进行des2的拷贝
      • 通过方法_Block_get_descriptor获取到Block_layout结构体中的成员struct Block_descriptor_1 *descriptor;
      • 通过内存平移的方式进行平移descriptor的大小,拿到des2。
      • 拷贝des2的内容,并且返回。
    • des2的get方法就是通过平移des1内存的大小得到的,这说明在内存布局时,des2紧跟着des1。
    • 而des3的获取经过了两步的判断,如果des2存在,那么des3 = des1的首地址+des1的size+des2的size,否则des3= des1首地址+des1的size。
    • 再进一步看des1,2,3的实现,发现似曾相识!!!!
      • des1内部成员是reserved、size是保留字段和描述block信息大小的size。
      • des2内部是copy、dispose 对应标记位为BLOCK_HAS_COPY_DISPOSE
      • des3内部是signature、layout对应的标记为BLOCK_HAS_SIGNATURE

    根据上边的des3的获取方法来看,判断des2是否存在的方法是和标志位BLOCK_HAS_COPY_DISPOSE进行与操作,所以可以看出来这两个标记为分别对应了des2和des3的内容。通过这两个个标志位就可以简洁的判断出des2和des3是否存在。

    在前边我们打印block时也发现有类似的key:signature我们再来看一下。


    des1、2、3布局打印
    • 相关参数源码解析
      在编译成c++文件后,block还传了几个参数,从编译后的文件可以看到,参数__main_block_desc_0_DATAcopy还有dispose有关系。

    __main_block_desc_0_DATA

    _main_block_desc_0_DATA = {
        0,
        sizeof(struct __main_block_impl_0),
        __main_block_copy_0,
        __main_block_dispose_0
    };
    

    __main_block_copy_0

    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
        _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    

    __main_block_dispose_0

    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
        _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    

    __main_block_copy_0调用了方法_Block_object_assign
    __main_block_dispose_0调用了方法_Block_object_dispose
    我们上源码中找一下这两个方法的具体实现。

    • _Block_object_assign
      方法_Block_object_assign有大量的注释,翻译了一下,虽然翻译的不好,也可能有错误。

    注释翻译

    当一个Block被拷贝到堆上是 可以引用四种不同的类型

    1. C++ stack based objects c++栈上的基础对象
    2. References to Objective-C objects 引用oc对象
    3. Other Blocks 其他的block
    4. __block variables __block变量

    在这些情境下 helper函数 会被编译器通过使用Block_copy和Block_release生成,调用copy和dispose的helpers。copy helper函数向c++堆栈对象发出构造函数调用, 其余会调用运行时来支持函数_Block_object_assign。 在情景一下dispose helper则会调用c++的析构函数并且会调用_Block_object_dispose函数。

    这些_Block_object_assign和_Block_object_dispose标志参数的是用来

    • BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object, 3:为了oc对象
    • BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and 7:为了>另外一个Block
    • BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable. 8: 为了__block的情况

    如果 __block变量被标记为weak,编译器也会是BLOCK_FIELD_IS_WEAK的情况。
    所以 block copy或者block dispose helpers 的值只可能是四个 3,7,8,24
    当 ——block作为参数,不论是c++对象,或者是一个oc对象,还是另外一个block,编译器都会生成copy、dispose helpers函数。

    和block copy helper函数相似,__block copy helper函数也会拷贝构造方法,__block dispose helper也会调用析构方法。 同样的 这些helpers函数 都会 调用 这两个带有 相同对象值和额外的带有128位信息支持的Blocks 的相同的支持方法。
    所以__block copy或者dispose 的helpers方法 将会分别生成 对象对应的3或者block对应的7,128总是会在信息里边, 下列的组合可能的情况是:

    __block id 128+3 (0x83)
    __block (^Block) 128+7 (0x87)
    __weak __block id 128+3+16 (0x93)
    __weak __block (^Block) 128+7+16 (0x97)

    方法解析
    方法_Block_object_assign的实现逻辑如下:
    1、普通对象类型(BLOCK_FIELD_IS_OBJECT),会交给方法_Block_retain_object处理,也就是arc自动管理。
    2、Block作为参数的类型(BLOCK_FIELD_IS_BLOCK),会调用方法_Block_copy。
    3、用__block修饰的类型(BLOCK_FIELD_IS_BYREF),调用方法_Block_byref_copy

    • _Block_object_dispose
      _Block_object_dispose的实现和_Block_object_assign是对应的。分别处理了BLOCK_FIELD_IS_BYREF、BLOCK_FIELD_IS_BLOCK、BLOCK_FIELD_IS_OBJECT的情况。
      1、BLOCK_FIELD_IS_BYREF情况,调用方法_Block_byref_release
      2、BLOCK_FIELD_IS_BLOCK情况,调用方法_Block_release
      3、BLOCK_FIELD_IS_OBJECT的情况,调用方法_Block_release_object

    这三个方法的解析会在后边的Block释放的部分讲。

    • _Block_byref_copy
      在方法_Block_object_assign,被__block修饰的时候,会调用方法_Block_byref_copy,它的具体实现如下:
    static struct Block_byref *_Block_byref_copy(const void *arg) {
        
        //先拿到参数里边的内容,是一个Block_byref结构体对象
        struct Block_byref *src = (struct Block_byref *)arg;
    
        // __block 内存是一样 同一个家伙
        //引用计数为0
        if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
            // src points to stack
            //开始拷贝参数
            //开辟参数一样的大小空间
            struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
            //修改 isa的指向
            copy->isa = NULL;
            // byref value 4 is logical refcount of 2: one for caller, one for stack
            //修改标志位,引用计数修改为2 ,一个是stack引用,一个是调用者引用了 所以是2 或上4是因为要把第0位空过去,第0位是表示是否释放的。
            copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
            //堆上的forwarding指向自己
            copy->forwarding = copy; // patch heap copy to point to itself
            //原来栈上的那个forwarding指向堆上的
            src->forwarding = copy;  // patch stack to point to heap copy
            //size赋值
            copy->size = src->size;
    
            //如果src有copy和dispose helpers
            if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                // Trust copy helper to copy everything of interest
                // If more than one field shows up in a byref block this is wrong XXX
                //拷贝Block_byref_2中的内容 这个block_bref也分为1,2,3
                struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
                struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
                copy2->byref_keep = src2->byref_keep;
                copy2->byref_destroy = src2->byref_destroy;
    
                //如果存在扩展内容 也都拷贝过来 也就是Block_byref_3
                if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                    struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                    struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                    copy3->layout = src3->layout;
                }
    
                // 捕获到了外界的变量 - 内存处理 - 生命周期的保存
                (*src2->byref_keep)(copy, src);
            }
            else {
                // Bitwise copy. 直接拷贝 这一次的拷贝中一定包含Block_byref_3
                // This copy includes Block_byref_3, if any.
                memmove(copy+1, src+1, src->size - sizeof(*src));
            }
        }
        // already copied to heap 如果已经在堆上了 引用计数加1
        else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
            latching_incr_int(&src->forwarding->flags);
        }
        
        return src->forwarding;
    }
    

    这个方法主要是对block传进来的参数进行处理,当有__block修饰的情况下,对传进来的Block_byref进行处理,前边已经对block本身还有它的des处理过了,然后现在是处理__block类型的参数。
    1、先进行引用计数的判断,如果引用计数为0,说明之前没有copy过,要进行参数拷贝的操作。

    • 开辟空间先拷贝Block_byref中的内容,Block_byref结构体的信息拷贝,栈上的forwarding指向改变指到新生成在堆上的这个。
    • 判断有没有Block_byref_2,如果有,直接拷贝Block_byref_2。
      • 然后判断有没有Block_byref_3,如果有拷贝Block_byref_3。
      • 处理byref_keep。
    • 如果没有,直接拷贝全部内容,这个里边一定包括Block_byref_3的内容。
      2、如果已经存在在堆上了,那么进行引用计数+1,不做别的操作。

    5.Block的释放

    前边在看方法_Block_object_dispose时遇到过下列这么三个方法,下边来看下具体实现。

    • _Block_release
    void _Block_release(const void *arg) {
        //获取block
        struct Block_layout *aBlock = (struct Block_layout *)arg;
        //不存在    啥也不做🤣
        if (!aBlock) return;
        //全局类型的 啥也不做🤣
        if (aBlock->flags & BLOCK_IS_GLOBAL) return;
        //不是堆类型 啥也不做🤣
        if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
        
        //根据dealloc那个标志位 判断是否需要释放
        if (latching_decr_int_should_deallocate(&aBlock->flags)) {
            //需要释放 调用释放helper
            _Block_call_dispose_helper(aBlock);
            //析构实例
            _Block_destructInstance(aBlock);
            //释放内存空间
            free(aBlock);
        }
    }
    

    如果是堆上的block,那么进行dispose_helper调用,调用析构函数,释放空间。

    如果不是堆上的,那就啥也不做。

    • _Block_byref_release
    static void _Block_byref_release(const void *arg) {
        struct Block_byref *byref = (struct Block_byref *)arg;
    
        // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
        //处理forwarding的指针引用计数
        byref = byref->forwarding;
        //堆类型的
        if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
            //获取引用计数
            int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
            os_assert(refcount);
            //判断是否需要释放
            if (latching_decr_int_should_deallocate(&byref->flags)) {
                //需要释放 并且有Block_byref_2
                if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                    //处理Block_byref_2
                    struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                    (*byref2->byref_destroy)(byref);
                }
                //释放空间
                free(byref);
            }
        }
    }
    

    简单总结
    block的拷贝进行三个阶段的拷贝
    1、block自身的内容拷贝
    2、block的des信息的拷贝,其中要去分别判断des2和des3然后通过内存平移的方式把des2或者des3的内容拷贝过来。
    3、block的参数拷贝,当__block修饰的变量被block捕获时,对__block的处理和对des的处理很像,也是一层一层的判断,判断bref_2是否存在,存在拷贝,否则全部momove参数内容,此时的参数内容是一定包含bref_3的。

    同样带有__block修饰的参数释放时也是一层一层判断进行析构的。

    附一张自己画的流程图:


    block底层调用流程图.jpg

    相关文章

      网友评论

        本文标题:29.iOS底层学习之block底层原理

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