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

本章提纲
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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容