欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS底层探索(二十二)Block

程序员文章站 2022-04-13 23:13:07
...

iOS底层探索(二十二)Block

Block相信都会用到,他是回传数据的一种方法,并且它本身是一个函数,只不过没有名字

Block的三种类型

自定义代码,如下

    void (^block)(void) = ^{
        NSLog(@"Name");
    };
    NSLog(@"%@",block);

这么一个简单的Block就写完了。运行一下。查看
iOS底层探索(二十二)Block

当前Block的类型为NSGlobalBlock类型。这是Block的其中一种类型,为全局类型。
当在Block访问外部变量时,再次打印。

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

运行结果为
iOS底层探索(二十二)Block

当前的BlockNSMallocBlock堆区的block。在iOS 14之前,当我们没有进行拷贝时,则是栈区类型。
iOS 14之后怎么获取栈区的block呢?
代码做如下修改,

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

运行查看结果
iOS底层探索(二十二)Block

当前的BlockNSStackBlock栈区的block。
从上方可知,如果没有捕获外部变量时,block会放在全局区
如果访问外部变量时,会将block进行copy,拷贝到相应的位置而是在堆区还是栈区则根据是否持有而改变,当前对象持有block时,block在堆区,否则在栈区

总结

  • NSGlobalBlock 全局区
  • NSMallocBlock 堆区
  • NSStackBlock 栈区

循环引用问题

正常释放

iOS底层探索(二十二)Block

A对象持有B对象,当A将要被释放时,将调用dealloc给B对象发信号。B对象执行dealoc方法进行释放。

循环引用

iOS底层探索(二十二)Block

其根本原因在于retainCount无法为0,dealloc方法无法调用。
循环引用的条件必须是,A对象强引用B对象,并且B对象强引用A对象才能成立循环引用。

weak-strong-dance 强弱共舞

自定义下面的循环引用代码。

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

上面代码就是一个循环引用,在敲出这段代码时,编译器也会报循环引用的警告,不去理他。
怎样打破它
我们知道,循环引用的根本原因在于self强引用block,并且block强引用self,那么在属性设定中不可以使用weakblock进行修饰,因为使用weak修饰时,出了创建block的作用域外,就会被自动释放。
block弱引用self

修改代码如下

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

将当前对象self使用__weak修饰一下,然后让block持有弱引用对象。__weak会将当前对象加入到弱引用表中。那么__weak是否会对当前对象的引用计数进行处理呢。
__weak的前后对引用计数进行打印。
iOS底层探索(二十二)Block

打印结果为一直,也就是说__weak并没有对当前对象的引用计数进行处理,也就是没有+1的操作。
但是这种方式有一个问题,就是weakSelf的对象当当前对象被释放时会自动释放,不管block内部是否完成了对weakSelf对象的访问。即做如下操作时,还没有打印name,就被释放了。

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

这种情况需要使用__strongweakSelf对象进行强引用,即在block的作用域结束前,当前对象不释放。

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

这种写法有个名字weak-strong-dance即强弱共舞。

__block捕获self

使用__block也可以进行打破循环引用的操作。代码如下

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

这种方式为block强引用vc,vc强引用self,那么在这个状态下依然存在循环引用,因此需要在vc使用完后,进行对vcnil,打破循环引用,其也是为手动打破循环引用。

传递self

我们将self变成函数的一个参数传入到block内部,那么参数的作用域即为当前block,当block执行完毕后,将自动置为nil,代码如下

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

Block探索

普通Block

创建.cC语言文件,做如下自定义代码。

#include "stdio.h"

int main() {
    void(^block)(void) = ^{
        printf("name");
    };
    block();
    return 0;
}

对上方的文件进行clang编译查看。
找到main函数,源码如下

int main(){
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    // 使用函数式进行调用。这里是block需要调用的原因。
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); // block为隐藏参数,类似于oc中的 id self, SEL _cmd一样。
    return 0;
}

对照源码查看。我们知道,block等于__main_block_impl_0,搜索__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;
    impl.Flags = flags;
    impl.FuncPtr = fp; // 函数式编程。即将函数变成参数传入。
    Desc = desc;
  }
};

可知__main_block_impl_0是一个结构体,这也是block能够使用%@进行打印的原因。当前的构造体也是一个函数,只不过没有名字。
查看__main_block_func_0方法,源码如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("name");
    }

当前方法的隐藏参数为block自己对象。

捕获变量Block

修改.c文件为如下格式,再次clang。查看编译后的结果

int main(){
    int a = 10;
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

查看__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;
  }
};

从代码中可知,在__main_block_impl_0结构体中可知,多了一个a的参数。也就是在编译时,就自动生成了相应的变量。
查看__main_block_func_0,即调用的方法中的源码,如下

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy

        printf("name = %d", a);
    }

由上方代码可知,在这个函数中a与在main函数中的a不是同一个变量,在我们的认知中如果是同一个变量应该传&a,即传递变量的地址,而现在的a只是与main函数中的a的值相同,但地址不同,也就是说,不是同一个a。也就是说,如果在block中对a进行操作,只是对block内部的a进行添加,跟main函数中的a基本无关。

__block捕获值

修改.c文件为如下。

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

进行clang编译查看main函数

int main(){
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

从上面的代码中,我们可以很清晰的看到&a的字样,这与上面的一致,即只有传递变量的地址时,才能真正的捕获变量。
查看__main_block_impl_0结构体。源码如下:

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_byref_a_0结构体的格式,源码如下:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

main中的赋值进行对照。可知将原来的a封装成对象。
查看__main_block_func_0方法源码,如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

        printf("name = %d", (a->__forwarding->a));
    }

在上面的amain中的a为同一个a,因为是对指针进行了拷贝。因此可以进行操作。

Block的签名

通过上方的分析,我们知道,block是对象,但是在上方代码中却没有感知到globalmallocstack三种类型的变化。以及block的调用。
自定义block,源码如下:

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

block1进行断点。并且开启汇编调试。
iOS底层探索(二十二)Block

进入到这块的汇编证明是第一次没有缓存的,如果有缓存则无法进入这块汇编中。
我们看到了objc_retainBlock方法的调用,做一个符号断点,重新运行。
跳入到了_Block_copy中,
iOS底层探索(二十二)Block

该方法存于libsystem_blocks.dylib中,至此我们知道了block的源码的位置。源码为libclosure-74
查找Block的结构Block_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
};

这是Block底层的真正形式。
我们继续断点研究。读取当前的寄存器。
iOS底层探索(二十二)Block

在这里我们可以看到当前的BlockNSGlobalBlock,并且Block的签名为aaa@qq.com?0
然后对block做如下修改,源码如下

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

打印寄存器的第一个值。如下
iOS底层探索(二十二)Block

它的类型为NSStackBlock,可是我们知道,当前的Block应该在堆区,即NSMallocBlock,因此我们可知在objc_retainBlock中将栈区拷贝到了堆区。
进入__Block_copy中,在retq中做断点,查看。
iOS底层探索(二十二)Block

这也就验证了__Block_copy中进行将block从栈区拷贝到堆区。
iOS底层探索(二十二)Block

iOS底层探索(二十二)Block

这两块代码用于研究。
接下来的操作就是获取Block_layout中的invoke进行调用操作。
Block_descriptor_1为必选参数。
Block_descriptor_2Block_descriptor_3为可选参数。
查看Block_descriptor_2的获取,在runtime.cpp文件中,源码如下:

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的值。如果没有则直接返回空,该函数为cpoydispose函数
查看Block_descriptor_3的获取函数,源码如下

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

如果没有则直接返回空,该函数与签名相关
block的结构图如下
iOS底层探索(二十二)Block

其中23可能不存在。那么我们进行获取时,需要进行平移获取。
确认当前的Block
iOS底层探索(二十二)Block

获取当前block的内存信息
iOS底层探索(二十二)Block

获取Block_descriptor_2
iOS底层探索(二十二)Block

打印结果可知,Block_descriptor_2为空。
获取Block_descriptor_3
iOS底层探索(二十二)Block

获取可知有值,并且可以获取到签名。
iOS底层探索(二十二)Block

Block_copy

我们已经知道了Block的调用,可是我们还不知道Block怎么从栈区拷贝的堆区的,从上面的分析中我们知道在底层调用_Block_copy函数之后出现了变化,我们直接查看Block的源码,找到_Block_copy方法,源码如下

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.
        // 栈区的block,因为无法直接创建堆区的block
        // 创建
        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.
        // invoke的拷贝
        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;
    }
}

这块的代码比较易读的。

block的三层拷贝

从之前clang编译出的的.cpp文件中,我们可以看到如下的代码

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*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

这两个函数中,从名字中我们可知是来自于Block_descriptor_2,因为在Block_descriptor_2中包含了两个方法copydispose
从编译后的源码中,可知,在进行copy时,使用_Block_object_assign进行对象的引用,我们在源码中找到该引用方式,源码如下。

// 捕获外界变量的操作。即 __block 的操作。
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: // 普通对象类型
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/
        // 系统中的源码没有处理,交给系统处理。
        _Block_retain_object(object);
        // 指针拷贝 持有
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK: // block类型
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/
        // 进行拷贝
        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF: // 使用__weak修饰对象
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/
        
        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break;
    }
}

查看BLOCK_FIELD的类型源码如下

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变量
    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.
};

查看_Block_byref_copy方法源码,如下

static struct Block_byref *_Block_byref_copy(const void *arg) {
    // Block_byref 结构体
    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_byref对象与外界持有的对象是同一个。
        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) {
            // 如果有持有能力
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            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;

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

查看上方代码中的Block_byref_2Block_byref_3的源码,如下

// 使用__block修饰就会存在的结构体
struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};
// 存在copy与dispose时存在
struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep; // 保存的结构体 
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};

修改对象类型,从clang文件中查找main函数,源码如下

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));

        __attribute__((__blocks__(byref))) __Block_byref_string_0 string = {(void*)0,(__Block_byref_string_0 *)&string, 33554432, 
        sizeof(__Block_byref_string_0), 
        __Block_byref_id_object_copy_131, 
        __Block_byref_id_object_dispose_131,
         ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), 
         sel_registerName("stringWithFormat:"), 
         (NSString *)&__NSConstantStringImpl__var_folders_nb_ghsrc7cn0xb_hgl5rt12y4340000gn_T_main_b3a94a_mi_0)
         };
        
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_string_0 *)&string, 570425344));
        
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

上方代码与之前相比多了__Block_byref_id_object_copy_131方法。再次查看__Block_byref_string_0结构体,源码如下

// 编译拷贝
struct __Block_byref_string_0 {
  void *__isa;
__Block_byref_string_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *string;
};

查看__Block_byref_id_object_copy_131方法,如下

// __block 修饰的变量,对byref结构体的拷贝
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
// dst 是当前的结构体,+40是为了进行内存平移,评一个的大小为__Block_byref_string_0找到最后的string的值
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

__Block_byref_string_0结构体中的string是在初始化中进行赋值的,赋值内容为

((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_nb_ghsrc7cn0xb_hgl5rt12y4340000gn_T_main_b3a94a_mi_0)

总结

  1. block自身的拷贝
  2. __block修饰的变量变成bref的结构体进行拷贝
  3. 如果bref中存在对象,即捕获的变量是对象变量时,通过bref结构体的内存平移,并且使用_Block_object_assign方法进行指针拷贝。