Block 的实质究竟是什么呢?类型?变量?还是什么黑科技?
Blocks 是 带有局部变量的匿名函数
Blocks 由 OC 转 C++ 源码方法
- 在项目中添加 blocks.m 文件,并写好 block 的相关代码。
- 打开「终端」,执行
cd XXX/XXX
命令,其中XXX/XXX
为 block.m 所在的目录。 - 继续执行
clang -rewrite-objc block.m
- 执行完命令之后,block.m 所在目录下就会生成一个 block.cpp 文件,这就是我们需要的 block 相关的 C++ 源码。
1. `/* 包含 Block 实际函数指针的结构体 */`
2. `struct __block_impl {`
3. `void *isa;`
4. `int Flags;`
5. `int Reserved; // 今后版本升级所需的区域大小`
6. `void *FuncPtr; // 函数指针`
7. `};`
9. `/* Block 结构体 */`
10. `struct __main_block_impl_0 {`
11. `// impl:Block 的实际函数指针,指向包含 Block 主体部分的 __main_block_func_0 结构体`
12. `struct __block_impl impl;`
13. `// Desc:Desc 指针,指向包含 Block 附加信息的 __main_block_desc_0() 结构体`
14. `struct __main_block_desc_0* Desc;`
15. `// __main_block_impl_0:Block 构造函数`
16. `__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {`
17. `impl.isa = &_NSConcreteStackBlock;`
18. `impl.Flags = flags;`
19. `impl.FuncPtr = fp;`
20. `Desc = desc;`
21. `}`
22. `};`
23. `/* Block 主体部分结构体 */`
24. `static void __main_block_func_0(struct __main_block_impl_0 *__cself) {`
25. `printf("myBlock\n");`
26. `}`
27. `/* Block 附加信息结构体:包含今后版本升级所需区域大小,Block 的大小*/`
28. `static struct __main_block_desc_0 {`
29. `size_t reserved; // 今后版本升级所需区域大小`
30. `size_t Block_size; // Block 大小`
31. `} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};`
32. `/* main 函数 */`
33. `int main () {`
34. `void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));`
35. `((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);`
36. `return 0;`
37. `}`
Block 结构体
我们先来看看 __main_block_impl_0
结构体( Block 结构体)
1. `/* Block 结构体 */`
2. `struct __main_block_impl_0 {`
3. `// impl:Block 的实际函数指针,指向包含 Block 主体部分的 __main_block_func_0 结构体`
4. `struct __block_impl impl;`
5. `// Desc:Desc 指针,指向包含 Block 附加信息的 __main_block_desc_0() 结构体`
6. `struct __main_block_desc_0* Desc;`
7. `// __main_block_impl_0:Block 构造函数`
8. `__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {`
9. `impl.isa = &_NSConcreteStackBlock;`
10. `impl.Flags = flags;`
11. `impl.FuncPtr = fp;`
12. `Desc = desc;`
13. `}`
14. `};`
从上边我们可以看出,__main_block_impl_0
结构体(Block 结构体)包含了三个部分:
从上述代码可以看出block的本质是个 __main_block_impl_0 的结构体对象,这就是为什么能用 %@ 打印出block的原因了
- 成员变量
impl
; - 成员变量
Desc
指针; __main_block_impl_0
构造函数。- 析构函数中所需要的函数:
fp
传递了具体的block实现__main_block_func_0
,然后保存在block结构体的impl
中
block捕获变量
这就说明了block声明只是将block实现保存起来,具体的函数实现需要自行调用
值得注意的是,当block为堆block时,block的构造函数会多出来一个参数a,并且在block结构体中多出一个属性a
接着把目光转向__main_block_func_0
实现
__cself
是__main_block_impl_0
的指针,即block本身int a = __cself->a
即int a = block->a
- 由于a只是个属性,所以是堆block只是值拷贝(值相同,内存地址不同)
- 这也是为什么捕获的外界变量不能直接进行操作的原因,如
a++
会报错
当__block修饰外界变量的时候
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q6SlRlhS-1690945388106)(https://raw.githubusercontent.com/ArnoVD97/PhotoBed/master/photo202307281627162.png)]
__block
修饰的属性在底层会生成响应的结构体,保存原始变量的指针,并传递一个指针地址给block——因此是指针拷贝
源码中增加了一个名为_Block_byref_a_0的结构体,用来保存我们要capture并且修改的变量i
__main_block_impl_0
引用的是_Block_byref_a_0结构体指针,起到修改外部变量的作用
_ Block_byref_a_0里面有isa,也是一个对象
我们需要负责_Block_byref_a_0结构体相关的内存管理,所以_main_block_desc_0中增加了copy和dispose的函数指针,用于在抵用前后修改相应变量的引用计数
struct __block_impl impl
说明
第一部分 impl
是 __block_impl
结构体类型的成员变量。__block_impl
包含了 Block 实际函数指针 FuncPtr
,FuncPtr
指针指向 Block 的主体部分,也就是 Block 对应 OC 代码中的 ^{ printf("myBlock\n"); };
部分。还包含了标志位 Flags
,今后版本升级所需的区域大小 Reserved
,__block_impl
结构体的实例指针 isa
。
1. `/* 包含 Block 实际函数指针的结构体 */`
2. `struct __block_impl {`
3. `void *isa; // 用于保存 Block 结构体的实例指针`
4. `int Flags; // 标志位`
5. `int Reserved; // 今后版本升级所需的区域大小`
6. `void *FuncPtr; // 函数指针`
7. `};`
struct __main_block_desc_0* Desc
说明
第二部分 Desc 是指向的是 __main_block_desc_0
类型的结构体的指针型成员变量,__main_block_desc_0
结构体用来描述该 Block 的相关附加信息:
- 今后版本升级所需区域大小:
reserved
变量。 - Block 大小:
Block_size
变量。
1. `/* Block 附加信息结构体:包含今后版本升级所需区域大小,Block 的大小*/`
2. `static struct __main_block_desc_0 {`
3. `size_t reserved; // 今后版本升级所需区域大小`
4. `size_t Block_size; // Block 大小`
5. `} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};`
__main_block_impl_0
构造函数说明
第三部分是 __main_block_impl_0
结构体(Block 结构体) 的构造函数,负责初始化 __main_block_impl_0
结构体(Block 结构体) 的成员变量。
1. `__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {`
2. `impl.isa = &_NSConcreteStackBlock;`
3. `impl.Flags = flags;`
4. `impl.FuncPtr = fp;`
5. `Desc = desc;`
6. `}`
关于结构体构造函数中对各个成员变量的赋值,我们需要先来看看 main()
函数中,对该构造函数的调用。
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
我们可以把上面的代码稍微转换一下,去掉不同类型之间的转换,使之简洁一点:
1. `struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);`
2. `struct __main_block_impl_0 myBlock = &temp;`
这样,就容易看懂了。该代码将通过 __main_block_impl_0
构造函数,生成的 __main_block_impl_0
结构体(Block 结构体)类型实例的指针,赋值给 __main_block_impl_0
结构体(Block 结构体)类型的指针变量 myBlock
。
可以看到, 调用 __main_block_impl_0
构造函数的时候,传入了两个参数。
- 第一个参数:
__main_block_func_0
。
- 其实就是 Block 对应的主体部分,可以看到下面关于__main_block_func_0
结构体的定义 ,和 OC 代码中^{ printf("myBlock\n"); };
部分具有相同的表达式。
- 这里参数中的__cself
是指向 Block 的值的指针变量,相当于 OC 中的self
。
c++ /* Block 主体部分结构体 */ static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("myBlock\n"); }
- 第二个参数:
__main_block_desc_0_DATA
:__main_block_desc_0_DATA
包含该 Block 的相关信息。
我们再来结合之前的__main_block_impl_0
结构体定义。
-__main_block_impl_0
结构体(Block 结构体)可以表述为:
1. `struct __main_block_impl_0 {`
2. `void *isa; // 用于保存 Block 结构体的实例指针`
3. `int Flags; // 标志位`
4. `int Reserved; // 今后版本升级所需的区域大小`
5. `void *FuncPtr; // 函数指针`
6. `struct __main_block_desc_0* Desc; // Desc:Desc 指针`
7. `};`
- __main_block_impl_0
构造函数可以表述为:
1. `impl.isa = &_NSConcreteStackBlock; // isa 保存 Block 结构体实例`
2. `impl.Flags = 0; // 标志位赋值`
3. `impl.FuncPtr = __main_block_func_0; // FuncPtr 保存 Block 结构体的主体部分`
4. `Desc = &__main_block_desc_0_DATA; // Desc 保存 Block 结构体的附加信息`
[[Block签名]]
__main_block_impl_0
结构体(Block 结构体)相当于 Objective-C 类对象的结构体,isa
指针保存的是所属类的结构体的实例的指针。_NSConcreteStackBlock
相当于 Block 的结构体实例。对象 impl.isa = &_NSConcreteStackBlock;
语句中,将 Block 结构体的指针赋值给其成员变量 isa
,相当于 Block 结构体的成员变量 保存了 Block 结构体的指针,这里和 Objective-C 中的对象处理方式是一致的。
也就是说明: Block 的实质就是对象。
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;
}
}
整段代码主要分成三个逻辑分支
- 通过
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的第一号位置已经存储着释放标记
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
- 是否是全局block——
else {
// Its a stack block. Make a copy.
size_t size = Block_size(aBlock);
struct Block_layout *result = (struct Block_layout *)malloc(size);
// 开辟堆空间
if (!result) return NULL;
memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#if __has_feature(ptrauth_signed_block_descriptors)
if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
uintptr_t oldDesc = ptrauth_blend_discriminator(
&aBlock->descriptor,
_Block_descriptor_ptrauth_discriminator);
uintptr_t newDesc = ptrauth_blend_discriminator(
&result->descriptor,
_Block_descriptor_ptrauth_discriminator);
result->descriptor =
ptrauth_auth_and_resign(aBlock->descriptor,
ptrauth_key_asda, oldDesc,
ptrauth_key_asda, newDesc);
}
#endif
#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
->堆block
的过程
- 先通过
malloc
在堆区开辟一片空间 - 再通过
memmove
将数据从栈区拷贝到堆区 invoke
、flags
同时进行修改- block的isa标记成
_NSConcreteMallocBlock
[[__block的深入研究]]