赞
踩
Block 的实质究竟是什么呢?类型?变量?还是什么黑科技?
Blocks 是 带有局部变量的匿名函数
cd XXX/XXX
命令,其中 XXX/XXX
为 block.m 所在的目录。clang -rewrite-objc block.m
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. `}`
我们先来看看 __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的构造函数会多出来一个参数a,并且在block结构体中多出一个属性a
接着把目光转向__main_block_func_0
实现
__cself
是__main_block_impl_0
的指针,即block本身int a = __cself->a
即int a = block->a
a++
会报错
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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_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
。__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
转换成到堆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;
}
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
同时进行修改_NSConcreteMallocBlock
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。