1.前導(dǎo)
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的編譯結(jié)果都有struct __main_block_impl_0
需要拷貝.struct __main_block_impl_0
的拷貝也就是block的拷貝的起點.
2. block的拷貝的起點
NSObject.mm內(nèi)的objc_retainBlock
在block進行賦值(賦__strong變量,__weak變量不適用)的時候就會調(diào)用.這也真是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環(huán)境下棧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
內(nèi)其他部分的代碼會和block的內(nèi)存管理相關(guān),下一篇文章會說.
3. block的拷貝的繼續(xù)
3.1 拷貝繼續(xù),所用方法梳理
// 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是標(biāo)記struct __main_block_impl_0是否需要針對內(nèi)部結(jié)構(gòu)再深入拷貝,
如果需要繼續(xù)調(diào)用結(jié)構(gòu)體的拷貝函數(shù)進行拷貝.
----------------------------------------------------------------------------------
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
的結(jié)果,有興趣可以回頭看看編譯的源碼.
block(int any)==>flag=0==>完結(jié)
block(NSString * any)==>flag=570425344==>繼續(xù)內(nèi)層拷貝
block(__block int any)==>flag=570425344==>繼續(xù)內(nèi)層拷貝
block(__block NSString * any)==>flag=570425344==>繼續(xù)內(nèi)層拷貝
可以看出除捕獲int any
的block在經(jīng)過一層拷貝之后完結(jié)外,其他的都仍然要繼續(xù).
block(int any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
------完結(jié)-----
-------------------------------------------------------------
block(NSString * any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
NSString * any需要拷貝==>繼續(xù)
block(__block int any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
struct __Block_byref_any_0需要拷貝==>繼續(xù)
block(__block NSString * any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
struct __Block_byref_any_0需要拷貝==>繼續(xù)
NSString * any需要拷貝
(*aBlock->descriptor->copy)
指向的方法前面的文章已經(jīng)有過代碼連線+示意圖,這里就不再說明了.
- block捕獲
對象類型
,編譯結(jié)果中有的對對象類型
的拷貝方法
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修飾的數(shù)據(jù)
,編譯結(jié)果中有的對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修飾的對象
,編譯結(jié)果中有的對struct __Block_byref_any_0內(nèi)部對象
的拷貝方法
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
編譯后的結(jié)構(gòu)體 __Block_byref_any_0
會比
__block int any
編譯后的結(jié)構(gòu)體 __Block_byref_any_0
多出兩個函數(shù)指針
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
對應(yīng)
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 所用方法內(nèi)部是什么
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);
}
他們里面調(diào)用的都調(diào)用了_Block_object_assign
.
各位注意:
_Block_object_assign
非常非常非常重要,可以說block捕獲參數(shù)的拷貝就是在_Block_object_assign
函數(shù)里"繞"!!!
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參數(shù)flag相關(guān)
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)
再看看先前相關(guān)總結(jié)的方法調(diào)用_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
三個參數(shù):
參數(shù)1:目標(biāo)地址
參數(shù)2:源對象地址
參數(shù)3:flog的不同確定后方走什么路
_Block_object_assign
與一系列其他函數(shù)的嵌套使用造就了block
對捕獲參數(shù)的拷貝.
4._Block_object_assign內(nèi)部流程
block(int)
block(NSString * any)
block(__block int)
block(__block NSString * any)
四小類block中后三種都會有對_Block_object_assign的調(diào)用,本文就不一一的說了,因為有很多重復(fù)的內(nèi)容.
本文以block(__block NSString * any)的拷貝來說明
__block NSString * any = [NSString stringWithFormat:@"1"];
void (^test)() = ^ {
NSLog(@"%@",any);
};
test();
4.1 調(diào)用__main_block_copy_0
調(diào)用方式:
__main_block_copy_0
調(diào)用_Block_object_assign
實現(xiàn):
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*/);
}
參數(shù)1:目標(biāo)__main_block_impl_0->__Block_byref_any地址
參數(shù)2:源__main_block_impl_0->__Block_byref_any地址
參數(shù)3: BLOCK_FIELD_IS_BYREF
用途:
拷貝結(jié)構(gòu)體__Block_byref_any
(結(jié)構(gòu)體內(nèi)含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內(nèi)部實現(xiàn)(已經(jīng)刪除GC相關(guān)內(nèi)容),所走的代碼已經(jīng)標(biāo)注.
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 調(diào)用__Block_byref_id_object_copy_131
4.2.1 如何調(diào)用到__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);
}
調(diào)用src(_Block_byref結(jié)構(gòu)體)
的byref_keep函數(shù)
_Block_byref結(jié)構(gòu)體
又哪來的byref_keep函數(shù)
?
看Block_private.h 與C++編譯的對比
Block_private.h內(nèi)
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++編譯內(nèi)
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
又指向誰呢?
當(dāng)然是靜態(tài)函數(shù)__Block_byref_id_object_copy_131
4.2.2 __Block_byref_id_object_copy_131具體分析
調(diào)用方式:
__Block_byref_id_object_copy_131
調(diào)用_Block_object_assign
實現(xiàn):
static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
關(guān)于(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個地址的字節(jié)數(shù)(32)+2個int的字節(jié)數(shù)(8)=40字節(jié),所以加40之后剛好指向NSString *any;
參數(shù)1:目標(biāo)__main_block_impl_0->__Block_byref_any->NSString * any
參數(shù)2:源__main_block_impl_0->__Block_byref_any->NSString * any
參數(shù)3: BLOCK_FIELD_IS_OBJECT||BLOCK_FIELD_IS_CALLER
用途:
拷貝__Block_byref_any
內(nèi)含的NSString * any
(內(nèi)部層次關(guān)系:__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修飾對象數(shù)據(jù)類型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)
的三層拷貝的過程中,我們也已經(jīng)對最開始提出的問題進行回答.
其他小類block拷貝的步驟是block(__block NSString * any)
的子集,有興趣可以自己梳理流程.
另外銷毀與拷貝是互逆的過程,所有的銷毀方法也在BlocksRuntime/runtime.c內(nèi)有興趣,可以再看看源碼.
- what's more!
block拷貝其實也是block內(nèi)存管理的一部分,下一篇將展開講講block的內(nèi)存管理.
參考文獻:
Block技巧與底層解析 by tripleCC