1.前导
1.1参考文档
参考文档1:BlocksRuntime/runtime.c
参考文档2:Block_private.h
1.2带入问题
Q1:栈block拷贝生成堆block的具体流程是怎样的?
Q2:为什么栈block拷贝生成堆block,栈block捕获的变量的__forwarding会指向堆上的变量?
block(int any)
struct __main_block_impl_0需要拷贝
block(NSString * any)
struct __main_block_impl_0需要拷贝
NSString * any需要拷贝
block(__block int any)
struct __main_block_impl_0需要拷贝
struct __Block_byref_any_0需要拷贝
block(__block NSString * any)
struct __main_block_impl_0需要拷贝
struct __Block_byref_any_0需要拷贝
NSString * any需要拷贝
四小类block的编译结果都有struct __main_block_impl_0
需要拷贝.struct __main_block_impl_0
的拷贝也就是block的拷贝的起点.
2. block的拷贝的起点
NSObject.mm内的objc_retainBlock
在block进行赋值(赋__strong变量,__weak变量不适用)的时候就会调用.这也真是block拷贝的开始.
//
// The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
//
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
void *_Block_copy(const void *arg) {
return _Block_copy_internal(arg, WANTS_ONE);
}
刨根问底会看到_Block_copy_internal
方法,忽略所有的GC代码,来个精简版本:
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
//printf("_Block_copy_internal(%p, %x)\n", arg, flags);
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;
}
// Its a stack block. Make a copy.
if (!isGC) {
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
}
功能很清晰,ARC环境下栈block
自动拷贝成堆block
会走:
// Its a stack block. Make a copy.
if (!isGC) {
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
以上代码很好的回答了Q1:栈block拷贝生成堆block的具体流程是怎样的?
.
_Block_copy_internal
内其他部分的代码会和block的内存管理相关,下一篇文章会说.
3. block的拷贝的继续
3.1 拷贝继续,所用方法梳理
// Its a stack block. Make a copy.
if (!isGC) {
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
注意代码:
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
BLOCK_HAS_COPY_DISPOSE是标记struct __main_block_impl_0是否需要针对内部结构再深入拷贝,
如果需要继续调用结构体的拷贝函数进行拷贝.
----------------------------------------------------------------------------------
block(NSString * any)
NSString * any = ((NSString *(*)(id, SEL, NSString *, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_qp_p2pj3jmj65n39jgl4wx9_l9w0000gn_T_main_18d483_mi_0);
void (*test)() = (
(void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA,
any,
570425344
)
);
----------------------------------------------------------------------------------
注意570425344.
enum {
BLOCK_REFCOUNT_MASK = (0xffff),
BLOCK_NEEDS_FREE = (1 << 24),
BLOCK_HAS_COPY_DISPOSE = (1 << 25),
BLOCK_HAS_CTOR = (1 << 26), /* Helpers have C++ code. */
BLOCK_IS_GC = (1 << 27),
BLOCK_IS_GLOBAL = (1 << 28),
BLOCK_HAS_DESCRIPTOR = (1 << 29)
};
570425344为(1 << 25 | 1 << 29),所以570425344==>BLOCK_HAS_COPY_DISPOSE|BLOCK_HAS_DESCRIPTOR
以上展示的是block(NSString * any)
传入的flag.下面直接给出四类Block
的结果,有兴趣可以回头看看编译的源码.
block(int any)==>flag=0==>完结
block(NSString * any)==>flag=570425344==>继续内层拷贝
block(__block int any)==>flag=570425344==>继续内层拷贝
block(__block NSString * any)==>flag=570425344==>继续内层拷贝
可以看出除捕获int any
的block在经过一层拷贝之后完结外,其他的都仍然要继续.
block(int any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
------完结-----
-------------------------------------------------------------
block(NSString * any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
NSString * any需要拷贝==>继续
block(__block int any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
struct __Block_byref_any_0需要拷贝==>继续
block(__block NSString * any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
struct __Block_byref_any_0需要拷贝==>继续
NSString * any需要拷贝
(*aBlock->descriptor->copy)
指向的方法前面的文章已经有过代码连线+示意图,这里就不再说明了.
- block捕获
对象类型
,编译结果中有的对对象类型
的拷贝方法
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->any, (void*)src->any, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
- block捕获
__block修饰的数据
,编译结果中有的对struct __Block_byref_any_0
的拷贝方法
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 8/*BLOCK_FIELD_IS_BYREF*/);
}
- block捕获
__block修饰的对象
,编译结果中有的对struct __Block_byref_any_0内部对象
的拷贝方法
static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
__block NSString * any
编译后的结构体 __Block_byref_any_0
会比
__block int any
编译后的结构体 __Block_byref_any_0
多出两个函数指针
struct __Block_byref_any_0 {
void *__isa;
__Block_byref_any_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);//多出的
void (*__Block_byref_id_object_dispose)(void*);//多出的
NSString *any;
};
__Block_byref_id_object_copy
与__Block_byref_id_object_copy_131
对应
block(NSString * any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
NSString * any需要拷贝 所用方法==>static void __main_block_copy_0
block(__block int any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
struct __Block_byref_any_0需要拷贝 所用方法==>static void __main_block_copy_0
block(__block NSString * any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
struct __Block_byref_any_0需要拷贝 所用方法==>static void __main_block_copy_0
NSString * any需要拷贝 所用方法==>static void __Block_byref_id_object_copy_131
3.2 所用方法内部是什么
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->any, (void*)src->any, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
他们里面调用的都调用了_Block_object_assign
.
各位注意:
_Block_object_assign
非常非常非常重要,可以说block捕获参数的拷贝就是在_Block_object_assign
函数里"绕"!!!
3.3 _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 *destAddr, const void *object, const int flags) {
//printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
_Block_assign_weak(object, destAddr);
}
else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
}
else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) {
// copying a __block reference from the stack Block to the heap
// flags will indicate if it holds a __weak reference and needs a special isa
_Block_byref_assign_copy(destAddr, object, flags);
}
// (this test must be before next one)
else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
// copying a Block declared variable from the stack Block to the heap
_Block_assign(_Block_copy_internal(object, flags), destAddr);
}
// (this test must be after previous one)
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
//printf("retaining object at %p\n", object);
_Block_retain_object(object);
//printf("done retaining object at %p\n", object);
_Block_assign((void *)object, destAddr);
}
}
_Block_object_assign参数flag相关
BLOCK_FIELD_IS_BLOCK (7)
BLOCK_FIELD_IS_BYREF (8)
BLOCK_FIELD_IS_CALLER (128)
BLOCK_FIELD_IS_OBJECT (3)
BLOCK_FIELD_IS_WEAK (16)
再看看先前相关总结的方法调用_Block_object_assign
用的都是什么flag
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
3==BLOCK_FIELD_IS_OBJECT
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 8/*BLOCK_FIELD_IS_BYREF*/);
}
8==BLOCK_FIELD_IS_BYREF
static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
131==BLOCK_FIELD_IS_OBJECT||BLOCK_FIELD_IS_CALLER;
_Block_object_assign
三个参数:
参数1:目标地址
参数2:源对象地址
参数3:flog的不同确定后方走什么路
_Block_object_assign
与一系列其他函数的嵌套使用造就了block
对捕获参数的拷贝.
4._Block_object_assign内部流程
block(int)
block(NSString * any)
block(__block int)
block(__block NSString * any)
四小类block中后三种都会有对_Block_object_assign的调用,本文就不一一的说了,因为有很多重复的内容.
本文以block(__block NSString * any)的拷贝来说明
__block NSString * any = [NSString stringWithFormat:@"1"];
void (^test)() = ^ {
NSLog(@"%@",any);
};
test();
4.1 调用__main_block_copy_0
调用方式:
__main_block_copy_0
调用_Block_object_assign
实现:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 8/*BLOCK_FIELD_IS_BYREF*/);
}
参数1:目标__main_block_impl_0->__Block_byref_any地址
参数2:源__main_block_impl_0->__Block_byref_any地址
参数3: BLOCK_FIELD_IS_BYREF
用途:
拷贝结构体__Block_byref_any
(结构体内含NSString * any
)
else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) {
// copying a __block reference from the stack Block to the heap
// flags will indicate if it holds a __weak reference and needs a special isa
_Block_byref_assign_copy(destAddr, object, flags);
}
_Block_byref_assign_copy内部实现(已经删除GC相关内容),所走的代码已经标注.
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg;
//printf("_Block_byref_assign_copy called, byref destp %p, src %p, flags %x\n", destp, src, flags);
//printf("src dump: %s\n", _Block_byref_dump(src));
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {<<4.1入口
//printf("making copy\n");
// src points to stack
bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
// if its weak ask for an object (only matters under GC)
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (isWeak) {
copy->isa = &_NSConcreteWeakBlockVariable; // mark isa field so it gets weak scanning
}
if (src->flags & BLOCK_HAS_COPY_DISPOSE) {<<4.2入口
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
copy->byref_keep = src->byref_keep;
copy->byref_destroy = src->byref_destroy;
(*src->byref_keep)(copy, src);
}
else {
// just bits. Blast 'em using _Block_memmove in case they're __strong
_Block_memmove(
(void *)©->byref_keep,
(void *)&src->byref_keep,
src->size - sizeof(struct Block_byref_header));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
// assign byref data block pointer into new Block
_Block_assign(src->forwarding, (void **)destp);
}
Q2解决:为什么栈block拷贝生成堆block,栈block捕获的变量的__forwarding会指向堆上的变量?下面的代码片段可以很好的解释.
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
src->forwarding = copy; // patch stack to point to heap copycopy->size = src->size;
到此为为止:
block(__block NSString * any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
struct __Block_byref_any_0需要拷贝 所用方法==>static void __main_block_copy_0 ==> 分析完毕
NSString * any需要拷贝 所用方法==>static void __Block_byref_id_object_copy_131
4.2 调用__Block_byref_id_object_copy_131
4.2.1 如何调用到__Block_byref_id_object_copy_131
if (src->flags & BLOCK_HAS_COPY_DISPOSE) {<<4.2入口
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
copy->byref_keep = src->byref_keep;
copy->byref_destroy = src->byref_destroy;
(*src->byref_keep)(copy, src);
}
调用src(_Block_byref结构体)
的byref_keep函数
_Block_byref结构体
又哪来的byref_keep函数
?
看Block_private.h 与C++编译的对比
Block_private.h内
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
int flags; /* refcount; */
int size;
void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
void (*byref_destroy)(struct Block_byref *);
/* long shared[0]; */
};
C++编译内
struct __Block_byref_any_0 {
void *__isa;
__Block_byref_any_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *any;
};
显而易见__Block_byref_id_object_copy
就是byref_keep
.
而__Block_byref_id_object_copy
又指向谁呢?
当然是静态函数__Block_byref_id_object_copy_131
4.2.2 __Block_byref_id_object_copy_131具体分析
调用方式:
__Block_byref_id_object_copy_131
调用_Block_object_assign
实现:
static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
关于(char*)dst + 40,(char*)src + 40的解释
struct __Block_byref_any_0 {
void *__isa;
__Block_byref_any_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);//多出的
void (*__Block_byref_id_object_dispose)(void*);//多出的
NSString *any;
};
4个地址的字节数(32)+2个int的字节数(8)=40字节,所以加40之后刚好指向NSString *any;
参数1:目标__main_block_impl_0->__Block_byref_any->NSString * any
参数2:源__main_block_impl_0->__Block_byref_any->NSString * any
参数3: BLOCK_FIELD_IS_OBJECT||BLOCK_FIELD_IS_CALLER
用途:
拷贝__Block_byref_any
内含的NSString * any
(内部层次关系:__main_block_impl_0->__Block_byref_any->NSString * any)
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
_Block_assign_weak(object, destAddr);
} else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
}
到此为止,捕获__block修饰对象数据类型block的拷贝3层,全部梳理完成.让我们再来看看示意图.
block(__block NSString * any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
struct __Block_byref_any_0需要拷贝 所用方法==>static void __main_block_copy_0 ==> 分析完毕
NSString * any需要拷贝 所用方法==>static void __Block_byref_id_object_copy_131 ==> 分析完毕
在分析block(__block NSString * any)
的三层拷贝的过程中,我们也已经对最开始提出的问题进行回答.
其他小类block拷贝的步骤是block(__block NSString * any)
的子集,有兴趣可以自己梳理流程.
另外销毁与拷贝是互逆的过程,所有的销毁方法也在BlocksRuntime/runtime.c内有兴趣,可以再看看源码.
- what's more!
block拷贝其实也是block内存管理的一部分,下一篇将展开讲讲block的内存管理.
参考文献:
Block技巧与底层解析 by tripleCC