美文网首页
Block探索

Block探索

作者: 半边枫叶 | 来源:发表于2020-03-08 12:09 被阅读0次

    Block的分类

    Block一共有6种类,常见的有三种。

    void (^block)(void) = ^{
        NSLog(@"1233");
    };
    

    上面这种block的打印结果为<__NSGlobalBlock__: 0x10cbfe088>,即为全局的block。

    NSLog(@"%@",^{
        NSLog(@"1233 - %d",a);
    });
    

    这样直接打印block打印出来的是<_NSStackBlock__: 0x7ffeeb763440>_,即为栈block。
    而如果将block赋值给block变量后,栈block就会被拷贝到堆上,成为了堆block,就像下面的这种block。

    int a = 10;
    void (^block)(void) = ^{
        NSLog(@"1233 - %d",a);
    };
    

    这种block的打印结果为<__NSMallocBlock__: 0x600002cd8090>,即为堆block。

    上面这三种block就是我们在开发中常见的三种block,除此之外还有三种系统的block。我们可以在block的源码中看到下面六中block。

    void * _NSConcreteStackBlock[32] = { 0 };
    void * _NSConcreteMallocBlock[32] = { 0 };
    void * _NSConcreteAutoBlock[32] = { 0 };
    void * _NSConcreteFinalizingBlock[32] = { 0 };
    void * _NSConcreteGlobalBlock[32] = { 0 };
    void * _NSConcreteWeakBlockVariable[32] = { 0 };
    

    Block的循环引用

    self.name = @"HelloBlock";
    self.block = ^{
        NSLog(@"%@",self.name);
    };
    self.block();
    

    我们知道上面的代码会引起循环引用。因为形成了self--->block--->self的循环引用。
    我们可以通过__weak来打破这种闭环。

    self.name = @"HelloBlock";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        NSLog(@"%@",weakSelf.name);
    };
    self.block();
    

    添加了__weak后虽然不会引起循环引用了。但是如果我们像下面代码一样进行了延迟调用,然后在延迟期间,我们退出了当前的viewController,self(即当前的ViewController)就会释放,然后打印weakSelf.name的结果就成为了null。

    self.name = @"HelloBlock";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakSelf.name);
        });
    };
    self.block();
    

    延迟打印的结果是null,这不是我们期望的结果。我们希望block打印完成后再释放self,这样既不会循环引用,也可以获取到正常的name的值。
    我们只需要在block里面加一个strongSelf,就可以解决。这样的话,block执行完成后才会给strongSelf发送release消息,strongSelf释放;然后viewController才会释放。这种方式属于中介者模式,使用到了weakSelf和strongSelf作为中介者。

    self.name = @"HelloBlock";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.name);
        });
    };
    self.block();
    

    我们还可以通过下面的这种中介者模式来防止循环引用,添加一个vc,然后在block执行完成后手动置为nil,从而打破了循环引用的闭环。但是这种方式一定要执行block,如果不执行的话,还是会导致循环引用。

    self.name = @"HelloBlock";
    __block ViewController *vc = self;
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
            vc = nil;
        });
    };
    self.block();
    

    我们还可以通过block传参的形式来防止循环引用问题,代码如下:

    self.block = ^(ViewController *vc){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
        });
    };
    

    再次我们总结一下防止循环引用的三种方式:

    • __weak;
    • 中介者模式,在block执行完成后手动置为nil;
    • block传参;

    Block的底层探索

    • 1、Block的本质是什么?
    • 2、Block为什么需要调用?
    • 3、Block自动捕获外界变量;
    • 4、__block的原理;

    block.c文件中内容如下

    #include "stdio.h"
    int main(){
        void(^block)(void) = ^{
            printf("HelloBlock");
        };
        
        block();
        return 0;
    }
    

    然后我们在终端中切换到该文件目录下,使用clang命令来探索一下这段代码对应的编译器底层的代码

    clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk block.c
    

    这样我们就得到了一个block.cpp文件,文件的主要内容如下(经过简化):

    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("HelloBlock");
    }
    
    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(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    
        block->FuncPtr(block);
        return 0;
    }
    

    该文件中的main函数对应我们block.c中的main函数。就是简单的申明block和调用block。

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

    这句为block的声明,通过上面的代码可知,其中的__main_block_impl_0是一个struct结构体,上面的代码就是构建了一个结构体。所以Block的本质就是结构体。构建结构体的时候传递了两个参数,一个是__main_block_func_0,另一个是__main_block_desc_0_DATA。其中的__main_block_func_0是一个打印的函数,也就是我们block块内的实际代码内容。

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

    __main_block_func_0保存在了FuncPtr属性中。调动Block的时候就是通过FuncPtr来调用的

    block->FuncPtr(block);
    

    所以Block的本质是结构体。block定义的时候将block函数保存到结构体中,然后block调用的时候,从结构体中取出函数进行调用。

    我们在block.c文件中添加一个外界变量a,然后在block中访问该变量a,然后再通过clang命令生成对应的block.cpp文件,关键的内容如下:

    // 申明Block时初始化的结构体
    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;
        //保存block的函数
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    // block函数内容
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int a = __cself->a; // bound by copy
        
            printf("HelloBlock - %d",a);
        }
    
    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 a = 10;
        //生命block
        void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);
    
        //调用block
        block->FuncPtr(block);
        return 0;
    }
    

    main函数后多了一个a变量,然后创建结构体的时候将a作为参数传递了进去,在结构体的构建方法的最后有个:a(_a),就是讲参数_a赋值给了__main_block_impl_0结构体的属性a。

    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;
        //保存block的函数
        impl.FuncPtr = fp;
        Desc = desc;
      }
    

    在block调用的函数中,将结构体属性a通过值拷贝赋值给了一个新的变量a。所以这样没法直接对外面我们自己的变量a进行操作。

    // block函数内容
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        int a = __cself->a; // bound by copy
        printf("HelloBlock - %d",a);
    }
    

    至此我们知道了block是怎么捕获外界变量的:通过生成一个属性来保存变量值。

    下面我们将外界变量a换成__block修饰,然后在block里面进行修改。代码如下

    int main(){
        __block int a = 10;
        void(^block)(void) = ^{
            a++;
            printf("HelloBlock - %d",a);
        };
        block();
        return 0;
    }
    

    对应的clang生命的c++代码如下:

    // 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;
      }
    };
    
    //block对应函数的调用
    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("HelloBlock - %d",(a->__forwarding->a));
    }
    
    // __block变量对应结构体的定义
    struct __Block_byref_a_0 {
        void *__isa;
        __Block_byref_a_0 *__forwarding;
        int __flags;
        int __size;
        int a;
    };
    
    int main(){
        // __block变量生成的结构体
        __Block_byref_a_0 a = {
            (void*)0,
            (__Block_byref_a_0 *)&a,
            0,
            sizeof(__Block_byref_a_0),
            10
        };
        
        // block的声明
        void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    
        // block的调用
        block->FuncPtr(block);
        return 0;
    }
    

    可见,__block修饰的变量a被操作成了一个结构体__Block_byref_a_0,该结构体中保存了变量a的地址和值。在block的回调函数中将结构体a的指针赋值给了a变量,然后通过a->__forwarding->a访问到结构体中保存的外面变量a的地址,然后实现对外面变量a的++操作。

    //block对应函数的调用
    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("HelloBlock - %d",(a->__forwarding->a));
    }
    

    __block修改外面变量的原理:为对应的__block变量生成了一个结构体,该结构体中保存了外界变量的指针和值,传递了一个指针递给给block函数调用。从而可以实现修改外面变量。

    block源码分析

    我们可以在openSource上找到block的开源源码libclosure。下面来看下源码内容:

    struct Block_layout {
        void *isa;
        volatile int32_t flags; // contains ref count
        int32_t reserved;
        BlockInvokeFunction invoke;
        struct Block_descriptor_1 *descriptor; //
        // imported variables
    };
    // 可选
    #define BLOCK_DESCRIPTOR_2 1
    struct Block_descriptor_2 {
        // requires BLOCK_HAS_COPY_DISPOSE
        BlockCopyFunction copy;
        BlockDisposeFunction dispose;
    };
    
    #define BLOCK_DESCRIPTOR_3 1
    struct Block_descriptor_3 {
        // requires BLOCK_HAS_SIGNATURE
        const char *signature;
        const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
    };
    

    Block_layout结构体就是block的结构。Block_descriptor_2和Block_descriptor_3是block的可选属性,block中是否存在这两个属性需要由Block_layout结构体中的flags属性来决定。

    flags的定义如下:

    // Values for Block_layout->flags to describe block objects
    enum {
        BLOCK_DEALLOCATING =      (0x0001),  // runtime
        BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
        BLOCK_NEEDS_FREE =        (1 << 24), // runtime
        BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
        BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
        BLOCK_IS_GC =             (1 << 27), // runtime
        BLOCK_IS_GLOBAL =         (1 << 28), // compiler
        BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
        BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
        BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
    };
    

    其中的BLOCK_HAS_COPY_DISPOSE用来标识是否存在Block_descriptor_2;;BLOCK_HAS_SIGNATURE用来标识是否存在Block_descriptor_3;BLOCK_IS_GLOBAL用来标识是否存在为全局的block。

    static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
    {
        if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
        uint8_t *desc = (uint8_t *)aBlock->descriptor;
        desc += sizeof(struct Block_descriptor_1);
        return (struct Block_descriptor_2 *)desc;
    }
    

    如果flags & BLOCK_HAS_COPY_DISPOSE为假,Block_descriptor_2返回为NULL。否则就可以通过Block_descriptor_1进行内存偏移访问到Block_descriptor_2.

    
    static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
    {
        if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
        uint8_t *desc = (uint8_t *)aBlock->descriptor;
        desc += sizeof(struct Block_descriptor_1);
        if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
            desc += sizeof(struct Block_descriptor_2);
        }
        return (struct Block_descriptor_3 *)desc;
    }
    

    如果flags & BLOCK_HAS_SIGNATURE为假,Block_descriptor_3返回为NULL。否则就可以通过Block_descriptor_1和Block_descriptor_2进行内存偏移访问到Block_descriptor_3。

    block由栈到堆

    下面我们使用Debug--Debug Workflow--Always show Disassembly来看下汇编,看下Debug怎么从StackBlock变成MallocBlock的。我们在block的地方打上断点。

    image.png
    然后打开汇编调试,运行就会定位到汇编代码:
    image.png
    我们看到汇编中有个objc_retainBlock,我们按住control,然后Step into跳转进去。此时使用LLDB命令,register read x0(读取x0的时候应该使用真机调试),读取到block为GlobalBlock。
    image.png
    然后我们将block代码改为访问外界变量a的block。
    int a = 10;
        void (^block1)(void) = ^{
            NSLog(@"LG_Block - %d", a);
        };
        block1();
    

    然后重新使用汇编调试,跳转到objc_retainBlock,打印x0信息,可以看到此时block变成了StackBlock类型


    image.png

    继续往下走,会走到Block_copy函数,此方法应该是拷贝block的方法,我们在该方法的最下面的return的地方打个断点,跳转到这个地方,然后read此时的x0(此时的x0即为返回值)。可以看到此时的x0变成了mallocBlock。


    image.png

    block签名

    下面我们看下Block的签名信息

    #define BLOCK_DESCRIPTOR_3 1
    struct Block_descriptor_3 {
        // requires BLOCK_HAS_SIGNATURE
        const char *signature;
        const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
    };
    
    struct Block_layout {
        void *isa;
        volatile int32_t flags; // contains ref count
        int32_t reserved;
        BlockInvokeFunction invoke;
        struct Block_descriptor_1 *descriptor; //
        // imported variables
    };
    

    我们从closure源码可以看到,block的signature签名位于Block_descriptor_3中,而Block_descriptor_3可以根据 Block_layout中的flags判断是否存在。下面我们通过汇编来追踪下signature的内容。
    最简单的方法,我们直接打印block也可以看到signature的内容:

    image.png
    我们还可以通过地址偏移来追踪signature的内容
    image.png
    上图中第一个x/4gx是打印的block结构体对象的内存地址,其中第四段是Block_descriptor_1的地址,然后接着我们使用x/4gx打印Block_descriptor_1的内容地址,通过flags可以知道Block_descriptor_2不存在,只存在Block_descriptor_1和Block_descriptor_3。其中的0x0000000000000020就是Block_descriptor_1,0x000000010078f34c就是Block_descriptor_3。 因为在Block_descriptor_3中第一个变量就是signature,所以直接就可以打印出signature了。
    现在我们知道了Block的签名形式为@?的形式,@代表对象,?代表未知的。就是指的block对象。

    block的拷贝

    // 栈 -> 堆 研究拷贝
    void *_Block_copy(const void *arg) {
        struct Block_layout *aBlock;
    
        if (!arg) return NULL;
        
        // The following would be better done as a switch statement
        aBlock = (struct Block_layout *)arg;
        if (aBlock->flags & BLOCK_NEEDS_FREE) {
            // latches on high
            latching_incr_int(&aBlock->flags);
            return aBlock;
        }
        else if (aBlock->flags & BLOCK_IS_GLOBAL) {//全局的block不需要拷贝,直接返回
            return aBlock;
        }
        else {
            // Its a stack block.  Make a copy.
            struct Block_layout *result =
                (struct Block_layout *)malloc(aBlock->descriptor->size);
            if (!result) return NULL;
            memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    #if __has_feature(ptrauth_calls)
            // Resign the invoke pointer as it uses address authentication.
            result->invoke = aBlock->invoke;
    #endif
            // reset refcount
            result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
            result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
            _Block_call_copy_helper(result, aBlock);
            // Set isa last so memory analysis tools see a fully-initialized object.
            result->isa = _NSConcreteMallocBlock;
            return result;
        }
    }
    

    我们closure源码中_Block_copy的方法内容很简单,就是判断如果是全局的block的话不需要拷贝,直接返回。否则就是栈block,在堆区创建一个,然后把invoke、flags、description等都拷贝过去,然后将isa设置为MallocBlock。这样就完成了block本身的拷贝。

    __block变量的堆拷贝

    __block NSString *lg_name = [NSString stringWithFormat:@"cooci"];
    void (^block1)(void) = ^{ // block_copy
        lg_name = @"LG_Cooci";
        NSLog(@"LG_Block - %@",lg_name);
    };
    block1();
    

    切换到文件目录下,使用clang命令xcrun -sdk iphoneSimulator clang -rewrite-objc main.m,就会生成main.cpp文件。在cpp文件中我们发现下面的与copy相关的函数

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

    然后调用了_Block_object_assign函数。我们到closure源码中查找这个函数

    void _Block_object_assign(void *destArg, const void *object, const int flags) {
        const void **dest = (const void **)destArg;
        switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
          case BLOCK_FIELD_IS_OBJECT:
                
            _Block_retain_object(object);
            *dest = object;
            break;
    
          case BLOCK_FIELD_IS_BLOCK:
    
            *dest = _Block_copy(object);
            break;
    
          case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
          case BLOCK_FIELD_IS_BYREF:
                
            *dest = _Block_byref_copy(object);
            break;
    
          default:
            break;
        }
    }
    

    switch判断,BLOCK_FIELD_IS_OBJECT为对象;BLOCK_FIELD_IS_BLOCK为block;BLOCK_FIELD_IS_BYREF为__block修饰的变量。之前我们研究了_Block_copy也就是block的拷贝。下面我们看下其他的拷贝。
    如果是BLOCK_FIELD_IS_BLOCK,也就是对象类型,调用了_Block_retain_object,但是该方法中什么都没有做,因为如果是对象类型的话会交给ARC来处理。
    如果是BLOCK_FIELD_IS_BYREF类型的话,也就是__block修饰的结构体变量,调用了_Block_byref_copy方法。

    static struct Block_byref *_Block_byref_copy(const void *arg) {
        struct Block_byref *src = (struct Block_byref *)arg;
    
        if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
            // src points to stack
            struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
            copy->isa = NULL;
            // byref value 4 is logical refcount of 2: one for caller, one for stack
            copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
            
            // 问题 - __block 修饰变量 block具有修改能力
            copy->forwarding = copy; // patch heap copy to point to itself
            src->forwarding = copy;  // patch stack to point to heap copy
            
            copy->size = src->size;
    
            if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                // ...... 省略了一些代码
    
                (*src2->byref_keep)(copy, src);
            }
            else {
                // Bitwise copy.
                // This copy includes Block_byref_3, if any.
                memmove(copy+1, src+1, src->size - sizeof(*src));
            }
        }
        // already copied to heap
        else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
            latching_incr_int(&src->forwarding->flags);
        }
        
        return src->forwarding;
    }
    

    使用 malloc在堆区拷贝了一份,然后赋值过去。有一点需要注意的是:

    copy->forwarding = copy; // patch heap copy to point to itself
    src->forwarding = copy;  // patch stack to point to heap copy
    

    不管是原来的__block结构体变量还是拷贝的__block结构体变量,都指向了copy的对象。这样就对应上了__block对象可以修改外界变量的部分。因为他们都指向了同一块堆区的地址。
    在堆__block结构体进行拷贝的方法中调用了下面的这句代码,从而完成了对__block结构体中变量进行了拷贝。

    (*src2->byref_keep)(copy, src);
    

    src中的byref_keep函数定义如下

    void(*BlockByrefKeepFunction)(struct Block_byref*, struct Block_byref*);
    

    src2的类型是Block_byref结构体。他的定义类型与Block结构体的定义类似,下面我们找打它的byref_keep函数位于Block_byref_2中,也就是第5个参数。

    struct Block_byref {
        void *isa;
        struct Block_byref *forwarding;
        volatile int32_t flags; // contains ref count
        uint32_t size;
    };
    
    struct Block_byref_2 {
        // requires BLOCK_BYREF_HAS_COPY_DISPOSE
        BlockByrefKeepFunction byref_keep;
        BlockByrefDestroyFunction byref_destroy;
    };
    

    我们到clang生成的cpp中找到对应的Block_byref_lg_name_0的第5个参数,即__Block_byref_id_object_copy_131。

    __Block_byref_lg_name_0 lg_name = {
        (void*)0,
        (__Block_byref_lg_name_0 *)&lg_name,
        33554432,
        sizeof(__Block_byref_lg_name_0),
        __Block_byref_id_object_copy_131, == keep
        __Block_byref_id_object_dispose_131,   
    };
    

    __Block_byref_id_object_copy_131``的定义如下,它又调用了我们上面分析的_Block_object_assign,传递的参数通过地址偏移找到了__block结构体中保存的外界___block变量。然后进行拷贝操作。最终完成了__block变量的修改。

    static void __Block_byref_id_object_copy_131(void *dst, void *src) {
        _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    }
    

    总结:Block的三层拷贝:1、Block结构体的拷贝;2、__block结构体的拷贝;3、__block结构体中的__block变量的拷贝 ;
    上面讲述了block的copy过程,block的释放过程和拷贝过程类似。

    相关文章

      网友评论

          本文标题:Block探索

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