“这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战”
前言
block底层源码block编译成一个什么样的结构blockinvoke-isa-签名-捕获-保存-释放
一.block通过clang分析
1.未捕获外部变量
int main(){
int a = 5;
void(^block)(void) = ^{
};
block();
return 0;
}
通过 xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc block.c -o block.cpp 生成.cpp文件 查看cpp文件
block是一个__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;
}
};
而我们的cpp调用的就是这个结构体中的构造函数__main_block_impl_0 该构造函数对block结构体中相关属性进行设置
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;//方法保存 block任务保存在FuncPtr中
Desc = desc;
}
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));block->FuncPtr(block);调用FuncPtr方法传入block
2.捕获未使用__block修饰的外部变量
int main(){
int a = 5;
void(^block)(void) = ^{
printf("%d",a);
};
block();
return 0;
}
通过上面的代码发现:
- 当捕获外部变量时,
block结构体中会多一个成员变量a,并且构造函数也会多一个参数a - 如果没有
__block修饰,则通过值拷贝的方式,对其成员变量a进行赋值 - 在执行
block任务时,从结构体中获取对应的成员变量__cself->a,进行处理 - 我们还发现编译时
isa指向的是一个栈block按以往这个应该是个堆block那么在运行时做了什么变为堆block
3.捕获使用__block修饰的外部变量
int main(){
__block int a = 5;
void(^block)(void) = ^{
a++;
printf("%d",a);
};
block();
return 0;
}
__block修饰的外部变量又有什么区别呢,见下面编译结果:
-
当外部变量使用
__block修饰时,会封装成一个结构体__Block_byref_a_0 -
block结构体中,多出一个属性a,属性a的类型为__Block_byref_a_0 -
a的地址会赋值到__Block_byref_a_0结构体的__forwarding属性中
二.block汇编分析得到签名copy过程
在block设置断点 查看汇编
设置
objc_retainBlock系统符号断点 继续运行
发现objc_retainBlock来自libobjc.A.dylib 也就是我们熟悉的objc库 在 libobjc.A.dylib源码里面查找objc_retainBlock
我们查看一下
_Block_copy 发现不属于libobjc.A.dylib
可以发现
_Block_copy在libsystem_blocks.dylib库中
通过跟踪汇编的方式,跟踪其内存变化过程。当程序运行到objc_retainBlock时,通过读取寄存器,分析block的数据状态变化。见下图:
调用objc_retainBlock方法时,此时依然是一个栈block。继续跟踪汇编,运行至_Block_copy,很显然block通过该方法完成了内存的变化,如何验证呢?汇编流程很长,在retq的地方设置断点,也就是在方法return的地方设置断点,查看其最终的处理结果,见下图:
同样通过读取寄存器x0,获取block此时为__NSMallocBlock__,并且地址发生了改变,从栈区拷贝到了堆区。
通过libclosure-79 搜索 _Block_copy 如下图:
至此,可以得出结论,捕获了外部变量的block,编译时是栈block,在运行时通过_Block_copy方法会copy到堆区,变成堆block。
**po [NSMethodSignature signatureWithObjCTypes:"v16@?0"]**
<NSMethodSignature: 0x60000043faa0>
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}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
block签名'@?'
三.blocklayout的结构
Block_layout中包括一些属性:
isaisa指向确定block类型flags标识码reserved保留字段invoke函数,也就是FuncPtrdescriptor相关附加信息block的Block_descriptor_1相关属性是必然存在,其中reserved为保留字段,size为block的大小;但是Block_descriptor_3是可选的参数。而这里就通过flag字段来判断block是否存在Block_descriptor_3的相关属性。Block_descriptor的get方法可以发现,通过地址平移的方式获取对应的值,并且在获取Block_descriptor_3时会判断Block_descriptor_2是否存在,如果不存在,就不需要添加Block_descriptor_2的地址空间。见下图:
我们可以通过
lldb进行相关的验证:
签名在
Block_descriptor_3的signature属性中。
四.block的捕获变量生命周期
block是如何捕获外部变量的呢,block三重拷贝过程是怎样的?回到cpp文件!查看__ViewController__viewDidLoad_block_desc_0结构体的定义。见下图:
该结构体即对应源码中的__ViewController__viewDidLoad_block_desc_0信息。其中reserved和size对应Block_descriptor_1的两个属性;另外,void (*copy)和void (*dispose)对应Block_descriptor_2的两个方法;在copy方法的实现中,会调用_Block_object_assign,此过程即为外部变量的捕获和释放过程。
在源码中全局搜索_Block_object_assign,得到以下注释信息:
_Block_object_assign和_Block_object_dispose的flags参数设置为:
BLOCK_FIELD_IS_OBJECT (3),捕获Objective-C Object的情况BLOCK_FIELD_IS_BLOCK (7),捕获另一个block的情况BLOCK_FIELD_IS_BYREF (8),捕获__block变量的情况 其枚举定义,见下图:
_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_default = fn (arc)
//如果持有变量是`BLOCK_FIELD_IS_OBJECT`类型,即没有`__block`修饰,指针指向该对象,将对该对象进行持有,引用计数加`1`
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
//如果是`BLOCK_FIELD_IS_BLOCK`类型,捕获一个`block`,则进行`_Block_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];
********/
//如果是BLOCK_FIELD_IS_BYREF,即有`__block`修饰,则会调用`_Block_byref_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;
}
}
_Block_byref_copy实现源码
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;
}
- 将外部对象封装成结构体
Block_byref *src - 如果是
BLOCK_FIELD_IS_BYREF,则会调用malloc,生成一个Block_byref *copy - 设置
forwarding,保证block内部和外部都指向同一个对象
copy->forwarding = copy;
src->forwarding = copy;
Block_byref中keep函数和destroy处理,并进行byref_keep函数的调用
Block_byref的设计思路和Block_layout中descriptor流程类似,通过byref->flag标识码判断对应的属性,以此来判断Block_byref_2是否存在,Block_byref定义见下图:
如果用__block修饰了外部变量,编译生成的cpp文件中,Block_byref结构体中就会默认生成两个方法,即对应Block_byref_2的keep方法和destory方法,见下图:
在cpp文件中搜索这两个函数的实现,见下图:
此过程会再次调用_Block_object_assign函数,对Block_byref结构体中的对象进行BLOCK_FIELD_IS_OBJECT流程处理。
至此block的三重拷贝流程如下:
block的拷贝,即将栈区block,拷贝至堆区__block修饰的对象,对应的Block_byref结构体的拷贝- 对
Block_byref修饰的对象,调用_Block_object_assign函数进行修饰处理