iOS探索 全方位解读Block

前言

相信大家对本文的主角block都有一定的了解,日常开发中也经常能看到它的身影。本文会从block概念blcok循环引用block底层三方面进行讲解

一、初识block

1.block定义

带有自动变量(局部变量)的匿名函数叫做block,又叫做匿名函数代码块

在不同语言中的叫法不同

程序语言 Block的名称
C Block
Smalltalk Block
Ruby Block
Python Lambda
C++ Lambda
JS Anonymous function

2.block分类

  • 全局block——__NSGlobalBlock__
void (^block)(void) = ^{
    NSLog(@"111");
};
NSLog(@"%@", block);
--------------------输出结果:-------------------
<__NSGlobalBlock__: 0x10a870050>
--------------------输出结果:-------------------
复制代码

  • 堆block——__NSMallocBlock__
int a = 0;
void (^block)(void) = ^{
    NSLog(@"%d", a);
};
NSLog(@"%@", block);
--------------------输出结果:-------------------
<__NSMallocBlock__: 0x600002dca2b0>
--------------------输出结果:-------------------
复制代码

  • 栈block——__NSStackBlock__
int a = 0;
NSLog(@"%@", ^{
    NSLog(@"%d", a);
});
--------------------输出结果:-------------------
<__NSStackBlock__: 0x7ffeec41e1b8>
--------------------输出结果:-------------------
复制代码

总结:

  • 不使用外界变量的block是__NSGlobalBlock__类型
  • 使用外界变量的block是__NSMallocBlock__类型
  • 在堆block拷贝前的block是__NSStackBlock__类型

除此之外,还有三种系统级别的block类型(能在libclosure源码中看到)

  • _NSConcreteAutoBlock
  • _NSConcreteFinalizingBlock
  • _NSConcreteWeakBlockVariable

二、block循环引用

1.循环引用的分析

image.png

这段代码就是传说中的循环引用,与此同时编译器也发出了警告

Capturing 'self' strongly in this block is likely to lead to a retain cycle
复制代码

那么就来分析一下循环引用的问题所在:

  • self持有了block
  • block持有了self(self.name)

这样就形成了self -> block -> self的循环引用

接下来不得不提到内存管理问题了(A引用B)

  • 正常释放时:A发送dealloc信号让Bdealloc

    image.png
  • 循环引用时:A、B互相引用,引用计数不能为0,dealloc不会被调用

    image.png

接下来就介绍一下解决循环引用的几种办法

2.循环引用的解决方法

2.1 强弱共舞
__weak typeof(self) weakSelf = self;
self.name = @"Felix";
self.block = ^{
    NSLog(@"%@", weakSelf.name);
};
复制代码

使用 中介者模式 __weak typeof(self) weakSelf = self将循环引用改为weakself -> self -> block -> weakself

表面看上去还是一个“引用圈”,但是weakself -> self这一层是弱引用——引用计数不处理,使用weak表管理。所以此时在页面析构时self就能正常的调用dealloc

但并不是最终的解决方案,此时仍存在着问题

__weak typeof(self) weakSelf = self;
self.name = @"Felix";
self.block = ^{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@", weakSelf.name);
    });
};
复制代码

如同这种延时情况,如若调用block之后立马返回上一页进行页面释放,3秒后weakself指向的self已经为nil了,此时的打印就只能打印出null

于是就有了强持有这么一说法

__weak typeof(self) weakSelf = self;
self.name = @"Felix";
self.block = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@", strongSelf.name);
    });
};
复制代码

再加一层临时的强持有,此时的引用就变成了strongself -> weakself -> self -> block -> strongself

看上去又是一个循环引用,但实际上strongSelf是个临时变量,当block作用域结束后就会释放,从而打破循环引用进行释放(让释放延后了3秒)

2.2 其他中间者模式

既然有“自动置空”,那么也可以“手动置空”

__block ViewController *vc = self;
self.name = @"Felix";
self.block = ^{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@", vc.name);
        vc = nil;
    });
};
复制代码

上述代码也是使用 中介者模式 打破循环应用的——使用vc作为中介者代替self从而打破循环引用

此时的引用情况为vc -> self -> block -> vc (vc在用完之后手动置空)

但是只要不调用block,仍然存在着循环应用

解决循环引用还有一种方式——不引用

self.name = @"Felix";
self.block = ^(ViewController *vc) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@", vc.name);
        vc = nil;
    });
}
复制代码

上述代码使用当前vc作为参数传入block时拷贝一份,就不会出现持有的情况,同时还能使用self的内存空间,能够完美避免循环引用

3.循环引用的补充说明

  • Masonry中是否存在循环引用?

Monsary使用的block是当做参数传递的,即便block内部持有self,设置布局的view持有block,但是block不持有view,当block执行完后就释放了,self的引用计数-1,所以block也不会持有self,所以不会导致循环引用

  • [UIView animateWithDuration: animations:]中是否存在循环引用?

UIView动画是类方法,不被self持有(即self持有了view,但view没有实例化)所以不会循环引用

  • 使用Facebook的开源框架能检测是否存在循环引用
- (void)checkLeaks {
    FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
    [detector addCandidate:self];
    NSSet *retainCycles = [detector findRetainCycles];
    NSLog(@"%@", retainCycles);
}
复制代码

三、block底层

1.block本质

int main(){
    __block int a = 10;
    void(^block)(void) = ^{
        a++;
        printf("Felix - %d",a);
    };
    block();
    return 0;
}
复制代码

clang将上述代码输出成cpp文件来查看底层实现

clang -rewrite-objc main.c -o main.cpp
复制代码

int main(){

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

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}
复制代码

  1. main函数中可以看到block的赋值是__main_block_impl_0类型,它是C++中的构造函数

全局搜索__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;
  }
};
复制代码

从上述代码可以看出block的本质是个 __main_block_impl_0 的结构体对象,这就是为什么能用 %@ 打印出block的原因了

  1. 再来看看析构函数中所需要的函数:fp传递了具体的block实现__main_block_func_0,然后保存在block结构体的impl

这就说明了block声明只是将block实现保存起来,具体的函数实现需要自行调用

  1. 当block为堆block时(外接传入变量)重新clang编译
int main(){

    int a = 10;
    void(^block)(void) = ^{
        printf("Felix %d ", a);
    };

    block();
    return 0;
}
复制代码

此时的block构造函数中就会多出一个参数a,并且在block结构体中也会多出一个属性a

image.png

接着把目光转向__main_block_func_0实现

  • __cself__main_block_impl_0的指针,即block本身
  • int a = __cself->aint a = block->a
  • 由于a只是个属性,所以是堆block只是值拷贝(值相同,内存地址不同)
  • 这也是为什么捕获的外界变量不能直接进行操作的原因,如a++会报错
  1. 当__block修饰外界变量时
int main(){

    __block int a = 10;
    void(^block)(void) = ^{
        printf("Felix %d ", a);
    };

    block();
    return 0;
}
复制代码

image.png

__block修饰的属性在底层会生成响应的结构体,保存原始变量的指针,并传递一个指针地址给block——因此是指针拷贝

2.block签名

接下来就来到libclosure源码中仔细看一看瞧一瞧

首先来看block结构体对象Block_layout(等同于clang编译出来的__Block_byref_a_0

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

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

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_layout是基础的block结构空间,而部分block则拥有Block_descriptor_2Block_descriptor_3结构,其中的flags标识记录了一些信息

  • 第1位:释放标记,一般常用BLOCK_NEEDS_FREE做位与操作,一同传入flags,告知该block可释放
  • 第16位:存储引用计数的值,是一个可选参数
  • 第24位:第16位是否有效的标志,程序根据它来决定是否增加火箭少女引用计数位的值
  • 第25位:是否拥有拷贝辅助函数
  • 第26位:是否拥有block析构函数
  • 第27位:标志是否有垃圾回收
  • 第28位:标志是否是全局block
  • 第30位:与BLOCK_USE_START相对,判断当前block是否拥有一个签名,用于runtime时动态调用

但部分block则拥有Block_descriptor_2Block_descriptor_3结构这句话又该怎么去理解呢?请看下面的解释

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

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;
}
复制代码

  • 如果aBlock->flags & BLOCK_HAS_COPY_DISPOSE满足,则_Block_descriptor_2存在,反之则block没有_Block_descriptor_2这个结构

    • _Block_descriptor_2可以通过Block_descriptor_1内存偏移得到
  • 同理,aBlock->flags & BLOCK_HAS_SIGNATURE满足,则_Block_descriptor_3存在

    • _Block_descriptor_3可以通过Block_descriptor_2内存偏移得到

决定这两个结构是否存在的绝对因素其实就是Block_layoutflags

// 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中的签名

  • NSGlobalBlock签名(_Block_copy进入时)
image.png
  • NSStackBlock签名(_Block_copy进入时)
image.png
  • NSMallocBlock签名(_Block_copy返回时)
image.png
image.png

最后通过内存平移也拿到了block的签名,可以看出

  • 这个block的返回值为空值
  • block的签名为@?,代表着不明对象

3.block的copy分析

接下来就来研究下栈block转换成到堆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) {
        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;
    }
}
复制代码

整段代码主要分成三个逻辑分支

  1. 通过flags标识位——存储引用计数的值是否有效

block的引用计数不受runtime处理的,是由自己管理的

static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return BLOCK_REFCOUNT_MASK;
        }
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            return old_value+2;
        }
    }
}
复制代码

这里可能有个疑问——为什么引用计数是 +2 而不是 +1

——因为flags的第一号位置已经存储着释放标记

  1. 是否是全局block——是的话直接返回block
  2. 栈block -> 堆block的过程
  • 先通过malloc在堆区开辟一片空间
  • 再通过memmove将数据从栈区拷贝到堆区
  • invokeflags同时进行修改
  • block的isa标记成_NSConcreteMallocBlock

4.__block的深入探究

为了更好地进行探究,我们在OCmain文件中进行clang编译

xcrun -sdk iphonesimulator clang -rewrite-objc main.m
复制代码

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);

        __block NSString *name = [NSString stringWithFormat:@"Felix"];
        void (^fxBlock)(void) = ^{ // block_copy
            name = @"Feng Felix";
        };
        fxBlock();
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
复制代码

4.1 第一层拷贝(block)

block中的第一层拷贝其实已经讲过了——_Block_copy将block从栈拷贝到堆

4.2 第二层拷贝(捕获变量的内存空间)
image.png

在函数声明时会传__main_block_desc_0_DATA结构体,在里面又会去调用__main_block_copy_0函数,__main_block_copy_0里面会调用_Block_object_assign——这就是第二层拷贝的调用入口

接下来就来看看_Block_object_assign在底层都做了什么(注意传参)

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:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

        *dest = _Block_copy(object);
        break;

      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // 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;
    }
}
复制代码

根据flags & BLOCK_ALL_COPY_DISPOSE_FLAGS进到不同分支来处理捕获到的变量

枚举值 数值 含义
BLOCK_FIELD_IS_OBJECT 3 对象
BLOCK_FIELD_IS_BLOCK 7 block变量
BLOCK_FIELD_IS_BYREF 8 __block修饰的结构体
BLOCK_FIELD_IS_WEAK 16 __weak修饰的变量
BLOCK_BYREF_CALLER 128 处理block_byref内部对象内存的时候 会加的一个额外的标记,配合上面的枚举一起使用

此时捕获到的变量是被__block修饰的BLOCK_FIELD_IS_BYREF类型,就会调用*dest = _Block_byref_copy(object);

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
        // 用原目标的大小在堆区生成一个Block_byref
        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具备了修改能力
        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;
}
复制代码

  • 用原目标name的大小在堆区生成一个Block_byref
  • copy->forwarding = copy; & src->forwarding = copy;——原来的区域和新的区域都指向同一个对象,使得block具备了修改能力
  • (*src2->byref_keep)(copy, src)开始第三层拷贝
4.3 第三层拷贝(拷贝对象)

(*src2->byref_keep)(copy, src)跟进去会来到Block_byref结构来,而byref_keepBlock_byref的第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;
};

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

复制代码

__block修饰的变量在底层其实调用了如下的构造方法——此时的第5位就等于byref_keep,所以在第二层拷贝时会调用__Block_byref_id_object_copy_131

image.png
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
复制代码

第五个函数会去调用_Block_object_assign函数

这个(char*)dst + 40看着觉得好莫名其妙啊...其实看到__Block_byref_name_0就顿悟了,刚好取得变量name对象

struct __Block_byref_name_0 {
  void *__isa;
__Block_byref_name_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *name;
};
复制代码

_Block_object_assign在对BLOCK_FIELD_IS_OBJECT情况时会做出如下操作:

case BLOCK_FIELD_IS_OBJECT:
    /*******
    id object = ...;
    [^{ object; } copy];
    ********/

    _Block_retain_object(object);
    *dest = object;
    break;
复制代码

  • _Block_retain_object是个空函数,因为block捕获的外接变量由ARC自动管理
  • 捕获到name进行拷贝
4.4 _Block_object_dispose

看完了三层拷贝,再来看一下释放函数_Block_object_dispose

void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}

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?)
    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)) {
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                (*byref2->byref_destroy)(byref);
            }
            free(byref);
        }
    }
}
复制代码

  • 如果是释放对象就什么也不做(自动释放)
  • 如果是__block修饰,就将指向指回原来的区域并使用free释放

5.总结

  • block的本质是个__main_block_impl_0的结构体对象,所以能用%@打印
  • block声明只是将block实现保存起来,具体的函数实现需要自行调用
  • block捕获外界变量时block结构体会自动生成一个属性来保存变量
  • __block修饰的属性在底层会生成响应的结构体,保存原始变量的指针,并传递一个指针地址给block
  • block中有三层拷贝:拷贝block、拷贝捕获变量的内存地址、拷贝对象

写在后面

block还有hook一块也是需要去学习了解的

小小的block也是有很多底层知识需要研究的,越学会发现自己越渺小,其实不然,只是你的视角开阔了,正是如此才会进步

以下文章可以做一个学习参考:
GCD面试要点
block面试要点
Runtime面试要点
RunLoop面试要点
内存管理面试要点
MVC、MVVM面试要点
网络性能优化面试要点
网络编程面试要点
KVC&KVO面试要点
数据存储面试要点
混编技术面试要点
设计模式面试要点
UI面试要点

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

推荐阅读更多精彩内容