前言
- iOS-block(上)对block种类进行了划分,由此也可以引出几个问题:
- 建立一个.c文件,包含了局部变量、block、block调用
- 通过xcrun编译为.cpp文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc block.c -o block.cpp
#include "stdio.h"
int main() {
int a = 18;
void(^block)(void) = ^{
printf("RaindyCool -");
};
block();
return 0;
}
- 在.cpp文件中找到main函数,将其强转等代码后进行精简
- block捕获变量会生成相应的成员变量,而等号右侧是函数指针
- block()调用相当于函数调用FuncPtr(block)
- block打印中加入对a的捕获printf(“RaindyCool - %d”, a),编译后得到.cpp文件
- impl.FuncPtr = fp 函数式保存,在block()调用时调用(__block_impl *)block
- 两个a的值相同,地址不同,所以是值拷贝
- 进一步改动,对a进行block修饰,同时在block中执行a++,此时编译后生成的是Block_byref_a_0
- Block_byref_a_0底层是结构体,其(Block_byref_a_0 *)类型的forwarding指针保存了传入的变量地址,然后通过参数_a->forwarding传递给了cself->a,使得block内部同外部可以访问同一片内存空间
- block主要作用,生成了一个block_byref_a_0的结构体,然后传给block指针地址
总结
- 当捕获外部变量时,block结构体中会增加一个成员变量a,并且构造函数也会增加一个参数a
- 如果没有__block修饰,则通过值拷贝方式,对成员变量a进行赋值
block执行时,通过cself->a,获取结构体中a的值,赋给Block_bref_a_0 *a
示例2
viewController建立如下代码,通过xcrun或clang编译得到.cpp文件 ```objectivec
(void)viewDidLoad { [super viewDidLoad]; NSObject *objc = [NSObject alloc];
__block NSObject *objc1 = [NSObject alloc]; void (^block1)(void) = ^{
NSLog(@"LG_Block %@ ",objc1);
}; block1();
}
- **编译的文件中,isa指向了栈block,在运行时,栈block变为堆**
- **fp是函数式保存,赋值给了impl -> FuncPtr,在block()调用时,调用FuncPtr,即调用fp**
![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1629812053932-16ee26d5-b9f6-49c1-b6f5-3d21fd445ae6.png#clientId=u49fed5a2-1f7c-4&from=paste&height=243&id=u35906799&margin=%5Bobject%20Object%5D&name=image.png&originHeight=486&originWidth=2606&originalType=binary&ratio=1&size=185948&status=done&style=none&taskId=u6ca93c4a-01ff-4b8b-82af-42668528b22&width=1303)
<a name="AbfhO"></a>
#### 总结
- block是一个结构体,也可以认为是一个函数或者参数,通过函数式编程,将block任务保存到block结构体的FuncPtr属性中。block()执行调用时即调用了impl->FuncPtr
![未命名文件 (7).jpg](https://cdn.nlark.com/yuque/0/2021/jpeg/21860440/1630033221144-efe5c0b3-4293-4872-8475-1f5d6a802509.jpeg#clientId=u6f551bc9-2a68-4&from=ui&id=uc03eebcc&margin=%5Bobject%20Object%5D&name=%E6%9C%AA%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6%20%287%29.jpg&originHeight=604&originWidth=984&originalType=binary&ratio=1&size=60758&status=done&style=none&taskId=u09ce35a2-f24e-4a25-8996-c21ea805749)
<a name="sja0p"></a>
## 底层源码
- **建立以下block代码,NSLog处断点调试,发现block底层是调用了objc_retainBlock**
```objectivec
NSObject *objc = [NSObject alloc];
__block NSObject *objc1 = [NSObject alloc];
void (^block1)(void) = ^{
NSLog(@"CC_Block %@ ",objc1);
};
block1();
objc_retainBlock 则是调用了_Block_copy(运行时调用),经过_Block_copy则将block从栈拷贝到了堆,但是在objc底层库中我们并未找到_Block_copy的调用,而_Block_copy是在libsystem_blocks.dylib库中,但该库并未开源。解决方案有两个:
- 反汇编查看
- 替代工程libclosure-79
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
libclosure-79源码中,_Block_copy调用如下
- 首先定义了Block_layout结构体(详解),对arg进行了类型转换
在编译器只有堆Block,运行时期发现是栈Block,同时还捕获了外部变量,则在堆上开辟内存空间,进行copy操作,同时将Block类型标记为堆Block ```objectivec // Copy, or bump refcount, of a block. If really copying, call the copy helper if present. 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. 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;
}
}
<a name="rQ4Ap"></a>
### 示例3
- 在viewDidLoad中加入以下代码,断点在block,运行
- 同时下符号断点_Block_copy
![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1629961337778-64fd34b5-0282-4fc0-b374-4a2805b3adf2.png#clientId=u2eeb6c64-fbca-4&from=paste&height=114&id=ubdfb0c35&margin=%5Bobject%20Object%5D&name=image.png&originHeight=228&originWidth=1312&originalType=binary&ratio=1&size=57787&status=done&style=none&taskId=ub549d4e6-7107-4f72-a1c2-e285a99b9ba&width=656)
- 通过register read 查看x0寄存器,po得到的内存地址
- 此时我们得到了一个栈Block,同时还展示了其包含的结构信息
- signature,block的签名,方法调用本质是查找消息,消息找不到时(比如hook)会进入消息转发流程,首先要获取其签名信息,才能包装成invocation进行相关处理,所以签名是必要信息
- po [NSMethodSignature signatureWithObjCTypes:"v8@?0"]查看内存信息![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1629962855286-82b6421b-b574-456e-a289-b1d6a43942a5.png#clientId=u2eeb6c64-fbca-4&from=paste&height=290&id=ub7e825b0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=580&originWidth=1778&originalType=binary&ratio=1&size=145973&status=done&style=none&taskId=u8264caeb-a402-473f-9dcd-6e8eaedad6e&width=889)
- invoke,函数的调用者指针
```objectivec
(lldb) register read x0
x0 = 0x000000016d85cec8
(lldb) po 0x000000016d85cec8
<__NSStackBlock__: 0x16d85cec8>
signature: "v8@?0"
invoke : 0x1025a4aa0 (/private/var/containers/Bundle/Application/38934E2E-F16E-4B76-AD32-3FBF58632FEC/005---GCD进阶使用(下).app/005---GCD进阶使用(下)`__29-[ViewController viewDidLoad]_block_invoke)
copy : 0x1025a4ae0 (/private/var/containers/Bundle/Application/38934E2E-F16E-4B76-AD32-3FBF58632FEC/005---GCD进阶使用(下).app/005---GCD进阶使用(下)`__copy_helper_block_e8_32r)
dispose : 0x1025a4b18 (/private/var/containers/Bundle/Application/38934E2E-F16E-4B76-AD32-3FBF58632FEC/005---GCD进阶使用(下).app/005---GCD进阶使用(下)`__destroy_helper_block_e8_32r)
(lldb)
- 继续执行,在_Block_copy结束时断点,查看x0,发现已经变为了堆block
Block_layout
- isa -> 指向,区分栈block、堆block
- flags -> 可以存储很多数据信息,包括引用计数
- reserved -> 流程数据
- invoke -> 调用函数
descriptor -> 相关描述,比如是否正在析构、是否有keep函数
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
};
Block_layout中除了Block_descriptor_1还有Block_descriptor_2、Block_descriptor_3的定义
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; // 签名定义在description3中 const char layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT };
//====================== static struct Block_descriptor_2 _Block_descriptor_2(struct Block_layout aBlock) { uint8_t desc = (uint8_t )_Block_get_descriptor(aBlock); desc += sizeof(struct Block_descriptor_1); return (struct Block_descriptor_2 *)desc; }
static struct Block_descriptor_3 _Block_descriptor_3(struct Block_layout aBlock) { uint8_t desc = (uint8_t )_Block_get_descriptor(aBlock); 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; }
- **Block_descriptor_2和Block_descriptor_3可以分别通过前一个地址计算得到,它们的地址是连续的**
![未命名文件 (6).jpg](https://cdn.nlark.com/yuque/0/2021/jpeg/21860440/1629982747785-b7a7fae4-79ae-4eab-9299-527290365492.jpeg#clientId=u66de2bc7-4021-4&from=ui&id=u6af7c23a&margin=%5Bobject%20Object%5D&name=%E6%9C%AA%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6%20%286%29.jpg&originHeight=618&originWidth=501&originalType=binary&ratio=1&size=30399&status=done&style=none&taskId=ub99186c8-fcc8-4527-a66f-9575b79d894)
<a name="GjZrm"></a>
## block捕获的变量的生命周期
- **继续观察上述.cpp文件,__main_block_desc_0中的数据结构即为Block_descriptor_1和Block_descriptor_2**
![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1629984290476-a9b38a62-d5ea-4b52-bff8-18e7200fbda8.png#clientId=u66de2bc7-4021-4&from=paste&height=233&id=u1c775bf9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=466&originWidth=2262&originalType=binary&ratio=1&size=148921&status=done&style=none&taskId=u001f6195-dc0a-44e5-8b07-1a4d3c10e4c&width=1131)
- __main_block_copy_0实现包含了_Block_object_assign,并且包含一个8参数,其定义如下
- The flags parameter of _Block_object_assign and _Block_object_dispose is set to,block对捕获变量的类型处理<br />* BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object, // 普通对象object<br />* BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and // 其它block修饰的<br />* BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable. // __block修饰的
![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1629984397357-f7dbc578-0c30-4452-a417-c492b9b9267a.png#clientId=u66de2bc7-4021-4&from=paste&height=54&id=ue9d234f7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=108&originWidth=1784&originalType=binary&ratio=1&size=61730&status=done&style=none&taskId=ua693fd59-5faa-41bb-a39e-1135b50a036&width=892)
- 不同修饰,block进行的底层操作不同
- BLOCK_FIELD_IS_OBJECT,仅进行指针拷贝
- BLOCK_FIELD_IS_BLOCK,将对象拷贝,全新地址
- BLOCK_FIELD_IS_BYREF,将对象和其指针一起拷贝,内部可修改外部(栈)的变量
![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1629985227375-2e62a973-3c4a-40c0-be40-3241c3893a84.png#clientId=u66de2bc7-4021-4&from=paste&height=692&id=ua9af9f2a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1384&originWidth=2142&originalType=binary&ratio=1&size=439749&status=done&style=none&taskId=ucafc3c69-4186-4b6c-a88b-2cec1261c75&width=1071)
- _Block_byref_copy实现了byref_keep,即对对象进行copy,保持其生命周期
- Block_byref_2包含两个参数,byref_keep和byref_destroy,在编译的.cpp文件中,分别被赋值__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131
```objectivec
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
// __block 内存是一样 同一个家伙
//
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// 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;
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;
}
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep; //= __Block_byref_id_object_copy_131
BlockByrefDestroyFunction byref_destroy; // = __Block_byref_id_object_dispose_131
};
- Block_byref_id_object_copy_131和Block_byref_id_object_dispose_131实现代码
- __Block_byref_id_object_copy_131中传入(char*)dst + 40相当于结构体内存平移,即object
- __block修饰的对象
- 首先进行栈->堆copy
- block捕获变量,捕获当前Block_byref,对Block_byref对象进行copy
- __Block_byref对其内部object进行变量copy
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);
}