Links

32.关于Block你所该知道的一切

一、 block 的分类

全局 block堆 block栈 block

1.1 NSGlobalBlock 全局 block

  • 位于全局区
  • 未捕获任何变量 或 只捕获全局静态变量
未捕获任何变量
1
<__NSGlobalBlock__: 0x100004030>
只捕获全局静态变量
4

1.2 堆 block

  • 位于堆区
  • block 内部使用 局部变量 或者 OC属性 ,并且赋值给 强引用 或者 Copy修饰 的变量
2
<__NSMallocBlock__: 0x1007049e0>

1.3 栈 block

  • 位于栈区
  • block 内部使用 局部变量 或者 OC属性 ,但 赋值给 强引用 或者 Copy修饰 的变量
3
<__NSStackBlock__: 0x7ffeefbff378>

1.4 面试题(一)

下面输出的结果会是什么样?
NSObject *obj = [NSObject new];
NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
void (^blockA)(void) = ^{
NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockA();
void (^ __weak blockB)(void) = ^{
NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockB();
void (^blockC)(void) = [blockB copy];
blockC();
这里涉及到关于 block类型 的底层知识,我们到最后在进行解析。

1.5 面试题(二)

下面输出的结果会是什么样?
int num = 0;
void (^ __weak blockA)(void) = nil;
{
void (^ __weak blockB)(void) = ^{
NSLog(@"%d", num);
};
blockA = blockB;
NSLog(@"=");
}
blockA();

1.6 面试题(三)

下面输出的结果会是什么样?
int num = 0;
void (^ __weak blockA)(void) = nil;
{
void (^ blockB)(void) = ^{
NSLog(@"%d", num);
};
blockA = blockB;
NSLog(@"=");
}
blockA();

二、 堆、栈block的生命周期

这里结合 二、三面试题 来进行探索。

2.1 面试题二解读

很可能有同学会给出只会打印 = 的答案。这就是对于 堆、栈block的生命周期 理解的不够深入。正确的输出如下
5
为什么呢?关键是对 blockB 的生命周期理解错误。一般会这么理解:
6
这里先不讲为什么,我们再看下 面试题三

2.2 面试题三解读

7
这里发生了野指针访问崩溃了。

2.3 堆 block 的生命周期

8
在大括号内部下断点可以看到, 这时 blockB 为 堆block 且有值。
9
赋值给 blockA
10
出了大括号后 blockA 为空了,说明 blockB 释放掉了。
因为作为 存储在堆上堆block 其生命周期仅存在于大括号内部。

2.4 栈 block 的生命周期

那么 面试题二 中的 blockB 的生命周期又是怎样的呢?
11
blockB栈block
12
赋值给 blockA
13
出了大括号的作用域了, blockA 的地址依旧没变,说明 blockB 没有被释放。
因为作为 存储在栈中栈block 其生命周期与 调用栈 相同。函数调用栈结束后释放。

三、 捕获变量的底层实现

3.1 普通变量的捕获

int num = 1;
void (^block)(void) = ^{
NSLog(@"%d", num);
};
block();
我们将上面的代码还原成 C++
14
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
  • block 的本质是一个结构体 __main_block_impl_0 ,如果有需要捕获的变量,结构体内部会生成一个成员用来存储,例子中是 int num;
  • __main_block_func_0 保存的是 block 的具体需要执行的函数
  • __block_implblock 的具体具体的一些数据,如函数等
  • __main_block_desc_0 是描述 block 特征的一个数据结构
这段代码中做了些什么呢?
一、 创建 block
1、 调用 __main_block_impl_0构造函数
2、 传入 __main_block_func_0 函数__main_block_desc_0_DATA 的地址变量 num
3、 指定 block 类型:impl.isa = &_NSConcreteStackBlock; (这里的堆block对应的是 _NSConcreteStackBlock
4、 存储标志位: impl.Flags = flags;
5、 存储函数: impl.FuncPtr = fp;
6、 描述内容赋值: Desc = desc;
二、 执行 block
1、 获取 __main_block_func_0 函数: ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)
2、 将 block 作为参数传入: ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
三、 执行函数 __main_block_func_0
1、 从 __main_block_impl_0 中取出变量 numint num = __cself->num;
2、 执行 NSLogNSLog((NSString *)&__NSConstantStringImpl__var_folders_py_7v1wvf813z5bmw0hr97yplwc0000gn_T_main_e543c2_mi_1, num);

3.2 __block 普通变量的捕获

__block int num = 0;
void (^block)(void) = ^{
num ++;
NSLog(@"%d", num);
};
block();
将上面的代码转为 C++
15
我们发现多了几种数据类型: __Block_byref_num_0__main_block_copy_0__main_block_dispose_0
加了 __block 的变量,这里被用 __Block_byref_num_0 数据结构包装了一下,这里传入的 num 也是通过地址的形式传入的。

3.3 捕获实例变量

不活实例变量和 3.2 没有什么区别。
NSNumber *num = @100;
void (^block)(void) = ^{
NSLog(@"%@", num);
};
block();
16

3.4 全局 block

static const NSString *string = @"aaaaaaa";
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
NSLog(@"%@", string);
};
block();
}
}
17

3.5 栈 block

int num = 0;
void (^ __weak block)(void) = ^{
NSLog(@"%d", num);
};
block();
这里还原 C++ 你可能会遇到包错:
error: cannot create __weak reference because the current deployment target does not support weak references
void (^ __attribute__((objc_ownership(weak))) block)(void)
加上一些参数即可:
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 main.m -o main.cpp
18
19
这里发现 __weak 实际是用 __attribute__((objc_ownership(weak))) 进行了包装,这里还没有什么更有用的线索,我们先继续探究。

3.6 _NSConcreteStackBlock

相信细心的你一定能发现一个问题,不论什么类型的 block ,还原出的 C++ 底层源码中都是 impl.isa = &_NSConcreteStackBlock; 看名字是个 栈 block 。和实际运行打断点运行时看到的不一样啊。下面我们就来分析一下这里发生了什么。

四、 运行时 block 类型的确定

由于这里并没有什么具体线索,我们通过下断点,并打开汇编。这里是一个 全局block
20

4.1 进入汇编断点

21

4.2 objc_retainBlock

22
通过 libobjc.A.dylib`objc_retainBlock: 发现它在 libobjc 中。
23
在 oc 源码中定位到了相关代码,但更进一步调用的 _Block_copy 并没有找到,我们继续回到汇编,继续执行下一步。

4.3 _Block_copy

24
这例进入了 _Block_copy 的汇编,发现它是 libsystem_blocks 中的,我们来 libclosure 中寻找一下线索,找到了下面的源码。
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 {
// 是一个 堆 block 就进行 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
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.
// 设置 isa 为 堆block
result->isa = _NSConcreteMallocBlock;
return result;
}
}

五、 汇编验证 block 的类型转换

5.1 全局block

25
一直来到 _Block_copy 内,读取 x0 寄存器 (ARM64下,x0这时存的是消息的接收者)。
26
进入 _Block_copy 时,就已经标识为 全局block 了。
我们发现输出了这样数据结构 signature invoke ,后面再具体看他们代表了什么。
<__NSGlobalBlock__: 0x1006f8028>
signature: "v8@?0"
invoke : 0x1006f5edc (/private/var/containers/Bundle/Application/49DA1CBF-A462-4433-80DE-19721201954A/Block探索.app/Block探索`__41-[ViewController touchesBegan:withEvent:]_block_invoke)

5.2 堆block

27
进入 _Block_copy 时,显示的是 栈block 。
28
<__NSStackBlock__: 0x16d6651b8>
signature: "v8@?0"
invoke : 0x10279de90 (/private/var/containers/Bundle/Application/A3A281E1-1993-4E28-8F30-3EED05043A20/Block探索.app/Block探索`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
和源码所写的一样,这里进行了 malloc :
29
_Block_copy return 之前我们再看一下 x0 :
30
这里已经变成了 堆block
<__NSMallocBlock__: 0x281030ab0>
signature: "v8@?0"
invoke : 0x10279de90 (/private/var/containers/Bundle/Application/A3A281E1-1993-4E28-8F30-3EED05043A20/Block探索.app/Block探索`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
_block 捕获变量的变化
进入 _Block_copy 时:
31
<__NSStackBlock__: 0x16b21d1a0>
signature: "v8@?0"
invoke : 0x104be5da0 (/private/var/containers/Bundle/Application/76397764-704E-4CA4-80F3-D94500849B81/Block探索.app/Block探索`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
copy : 0x104be5e04 (/private/var/containers/Bundle/Application/76397764-704E-4CA4-80F3-D94500849B81/Block探索.app/Block探索`__copy_helper_block_e8_32r)
dispose : 0x104be5e3c (/private/var/containers/Bundle/Application/76397764-704E-4CA4-80F3-D94500849B81/Block探索.app/Block探索`__destroy_helper_block_e8_32r)
_Block_copy return 之前
32
<__NSMallocBlock__: 0x2823a83f0>
signature: "v8@?0"
invoke : 0x100825da0 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/Block探索.app/Block探索`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
copy : 0x100825e04 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/Block探索.app/Block探索`__copy_helper_block_e8_32r)
dispose : 0x100825e3c (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/Block探索.app/Block探索`__destroy_helper_block_e8_32r)
这里的数据结构又发生了变化,多了 copy dispose

5.3 栈block

33
通过查看调用栈,我们发现并没有调用 objc_retainBlock 。没有进行 _Block_copy 操作。

六、 Block_layout 结构分析

35

6.1 isa

block 的类型:全局、堆、栈

6.2 flags

这些标志位都标明了不同的使用时间,有 运行时:runtime编译期:compiler
// 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_descriptor_2
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
/// 是否拥有 block 析构函数
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
/// 标志是否有垃圾回收 MAC
BLOCK_IS_GC = (1 << 27), // runtime
/// 是否是 全局block
BLOCK_IS_GLOBAL = (1 << 28), // compiler
/// 和 BLOCK_HAS_SIGNATURE 对应
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
/// 有签名 有的话就有 Block_descriptor_3
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
/// 有拓展 Layout
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};

6.3 reserved

保留字段

6.4 invoke

函数指针,即block中具体需要执行的内容。

6.5 descriptor

这里我们在看下其中的 Block_descriptor
36
这里就和我们之前输出的内容对应上了:
<__NSMallocBlock__: 0x2823a83f0>
signature: "v8@?0"
invoke : 0x100825da0 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/Block探索.app/Block探索`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
copy : 0x100825e04 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/Block探索.app/Block探索`__copy_helper_block_e8_32r)
dispose : 0x100825e3c (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/Block探索.app/Block探索`__destroy_helper_block_e8_32r)
37
虽然 Block_layout 的内存结构中只有 Block_descriptor_1 ,但是通过解读源码,可以发现, Block_descriptor_2Block_descriptor_3 的读取是通过内存平移实现的,具体有哪些数据可读则是根据 标识位 来判别。
Block_descriptor_1
基础数据,一定有的
Block_descriptor_2
COPY 相关字段,有 BLOCK_HAS_COPY_DISPOSE 标志位时有。
Block_descriptor_3
签名相关数据,有 BLOCK_HAS_SIGNATURE 标志位时有。

七、 block 签名

上面的 Block_descriptor_3 中咱们提到了签名。如上面例子输出的 signature: "v8@?0" 很眼熟,它其实是 Type Encodings 类型编码,我们在 属性 中有遇到过。
  • v8@?0
    • v 表示返回值为空
    • 8 表示参数的总大小
    • @? 表示block,
    • 0 表示从0号字节开始
具体可以对照官方文档
NSMethodSignature
我们也可以通过 NSMethodSignature 来输出详细它的代表信息。
(lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"]
<NSMethodSignature: 0xac5982543c221e27>
number of arguments = 1
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (@) '@?'
flags {isObject, isBlock} // 是个对象,也是一个 block
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}

八、 __block 捕获变量的生命周期

38
我们再来研究下捕获变量的原理,我们继续通过汇编探索。
39
这里会调用到 _Block_object_assign
我们找到相关源码:

8.1 捕获变量类型

// Values for _Block_object_assign() and _Block_object_dispose() parameters
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
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.
};

8.2 _Block_object_assign

//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
//
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 的实现:
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
_Block_retain_object_default 的实现:
static void _Block_retain_object_default(const void *ptr __unused) { }
其实什么都没做
*/
_Block_retain_object(object);
// 指针拷贝,引用计数 +1
*dest = object;
break;
// 二、 捕获的是 block
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
/**
前文有关于 _Block_copy 的讲解么这里就不多说了
*/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// 三、 捕获的是 __block / __weak __block 修饰的变量
/*******
// 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;
}
}

8.3 _Block_byref_copy

__Block 捕获外界变量的操作 内存拷贝 等
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) {
// 如果是通过引用计数管理的对象,就进行 malloc ,创建一个 copy
// 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;
/**
统一 forwarding 指向 copy
所以 __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;
}
/**
捕获到了外部变量
调用 byref_keep
内存处理
被捕获变量生命周期的保存
*/
(*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 结构体,还有个一个 byref_keep 与生命周期相关的函数调用,但是他们的来源并没有什么线索,我们只能继续从 C++ 中寻找一些线索。

8.4 Block_byref

其中除了 Block_byref 还有 Block_byref_2Block_byref_3 。他们和前面的 Block_descriptor_1 一样,也是通过标志位区分,通过内存平移进行读写的
// __Block 修饰的结构体
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
// __Block 修饰的结构体 byref_keep 和 byref_destroy 函数 - 来处理里面持有对象的保持和销毁
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
// 拓展 layout
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};

8.5 通过 C++ 理解 Block_byref 结构

位了更深入的理解 Block_byref 的结构,我们通过还原 C++ 找一下线索:
__block NSNumber *num = @100;
void (^block)(void) = ^{
num = @101;
NSLog(@"%@", num);
};
block();
/**
对应的 Block_byref 类型结构体
*/
struct __Block_byref_num_0 {
void *__isa;
__Block_byref_num_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSNumber *num;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_num_0 *num; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
...
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// Block_byref 构造函数
__attribute__((__blocks__(byref))) __Block_byref_num_0 num = {
/**
Block_byref 的
void *isa;
*/
(void*)0,
/**
Block_byref 的
struct Block_byref *forwarding;
*/
(__Block_byref_num_0 *)&num,
/**
Block_byref 的
volatile int32_t flags;
*/
33554432,
/**
Block_byref 的
uint32_t size;
*/
sizeof(__Block_byref_num_0),
/**
Block_byref_2 的
BlockByrefKeepFunction byref_keep;
*/ __Block_byref_id_object_copy
__Block_byref_id_object_copy_131,
/**
Block_byref_2 的
BlockByrefDestroyFunction byref_destroy;
*/
__Block_byref_id_object_dispose_131,
// *num
((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 100)
};
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
byref_keep
再次调用 _Block_object_assign (详见 8.2),这时传入的 flag131 ,二进制为 1000 0011,而 BLOCK_FIELD_IS_OBJECT = 3 ,所以会执行 BLOCK_FIELD_IS_OBJECT 相关的分支,进行 拷贝引用计数 + 1
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
byref_destroy
调用 _Block_object_dispose 进行销毁
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

九、 补充:面试题一解析

40

这里是 1 应该都没什么疑问

NSObject *obj = [NSObject new];
NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));

这里为什么是 3 呢?

void (^blockA)(void) = ^{
NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockA();
  • 这是引用了一个局部变量
    • 内部创建了一个结构体对 obj 进行了 引用 ,引用计数+1
  • 这个 block强引用 ,它是一个 堆block
    • 从上面的源码学习中我们得知, block 的类型是运行时确定的
    • 而在这个过程中,进行了 _Block_copy,引用计数 +1
  • 所以这里打印出了 3

为什么是 4

void (^ __weak blockB)(void) = ^{
NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockB();
  • 这是引用了一个局部变量
    • 内部创建了一个结构体对 obj 进行了 引用 ,引用计数+1
  • 这个 block弱引用 ,它是一个 栈block,引用计数不变

为什么是5

void (^blockC)(void) = [blockB copy];
blockC();
  • 将 栈blockB 进行了 copy,引用计数 +1

总结

__block 捕获变量进行了三重拷贝:
  • 变量A__block 修饰,就会被 blockB拷贝
    • _Block_copy
  • blockB 捕获 Block_byref C拷贝
    • _Block_object_assign - _Block_byref_copy
  • Block_byref C 对捕获的 变量A 进行拷贝
    • _Block_object_assign - byref_keep

参考