iOS中block介紹(四)揭開神祕面紗(下)
2013-07-19 14:35 佚名 dreamingwish 字號:T | T
終於有空開始這系列最後一篇的編寫。這一篇,我們將看到block的內存管理的內部實現,通過剖析runtime庫源碼,我們可以更深刻的理解block的內存運作體系。
看此篇時,請大家同時打開兩個網址(或者下載它們到本地然後打開):
http://llvm.org/svn/llvm-project/compiler-rt/trunk/BlocksRuntime/runtime.c
http://llvm.org/svn/llvm-project/compiler-rt/trunk/BlocksRuntime/Block_private.h
內存管理的真面目
objc層面如何區分不同內存區的block
Block_private.h中有這樣一組值:
/* the raw data space for runtime classes for blocks */ /* class+meta used for stack, malloc, and collectable based blocks */ BLOCK_EXPORT void * _NSConcreteStackBlock[32]; BLOCK_EXPORT void * _NSConcreteMallocBlock[32]; BLOCK_EXPORT void * _NSConcreteAutoBlock[32]; BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]; BLOCK_EXPORT void * _NSConcreteGlobalBlock[32]; BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
其用於對block的isa指針賦值
1.棧
struct __OBJ1__of2_block_impl_0 { struct __block_impl impl; struct __OBJ1__of2_block_desc_0* Desc; OBJ1 *self; __OBJ1__of2_block_impl_0(void *fp, struct __OBJ1__of2_block_desc_0 *desc, OBJ1 *_self, int flags=0) : self(_self) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
在棧上創建的block,其isa指針是_NSConcreteStackBlock。
2.全局區
在全局區創建的block,其比較類似,其構造函數會將isa指針賦值爲_NSConcreteGlobalBlock。
3.堆
我們無法直接創建堆上的block,堆上的block需要從stack block拷貝得來,在runtime.c中的_Block_copy_internal函數中,有這樣幾行:
// Its a stack block. Make a copy. if (!isGC) { struct Block_layout *result = malloc(aBlock->descriptor->size); ... result->isa = _NSConcreteMallocBlock; ... return result; }
可以看到,棧block複製得來的新block,其isa指針會被賦值爲_NSConcreteMallocBlock
4.其餘的isa類型
BLOCK_EXPORT void * _NSConcreteAutoBlock[32]; BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]; BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
其他三種類型是用於gc和arc,我們暫不討論
複製block
對block調用Block_copy方法,或者向其發送objc copy消息,最終都會調用runtime.c中的_Block_copy_internal函數,其內部實現會檢查block的flag,從而進行不同的操作:
static void *_Block_copy_internal(const void *arg, const int flags) { ... aBlock = (struct Block_layout *)arg; ... }1.棧block的複製 // 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 }
除了修改isa指針的值之外,拷貝過程中,還會將BLOCK_NEEDS_FREE置入,大家記住這個值,後面會用到。
最後,如果block有輔助copy/dispose函數,那麼輔助的copy函數會被調用。
2.全局block的複製
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}全局block進行copy是直接返回了原block,沒有任何的其他操作。
全局block進行copy是直接返回了原block,沒有任何的其他操作。
3.堆block的複製
if (aBlock->flags & BLOCK_NEEDS_FREE) { // latches on high latching_incr_int(&aBlock->flags); return aBlock; }
棧block複製時,置入的BLOCK_NEEDS_FREE標記此時起作用,_Block_copy_internal函數識別當前block是一個堆block,則僅僅增加引用計數,然後返回原block。
輔助copy/dispose函數
1.普通變量的複製
輔助copy函數用於拷貝block所引用的可修改變量,我們這裏以 __block int i = 1024爲例:
先看看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]; */ };
而我們的__block int i = 1024的轉碼:
struct __Block_byref_i_0 { void *__isa; __Block_byref_i_0 *__forwarding; int __flags; int __size; int i; };//所以我們知道,當此結構體被類型強轉爲Block_byref時,前四個成員是一致的,訪問flags就相當於訪問__flags,而內部實現就是這樣使用的 ... __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024};//i初始化時__flags爲0static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
此時,複製時調用的輔助函數:
void _Block_object_assign(void *destAddr, const void *object, const int flags) {//此處flags爲8,即BLOCK_FIELD_IS_BYREF ... 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); } ... } static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {//此處flags爲8,即BLOCK_FIELD_IS_BYREF struct Block_byref **destp = (struct Block_byref **)dest; struct Block_byref *src = (struct Block_byref *)arg; ... else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {//當初次拷貝i時,flags爲0,進入此分支會進行復制操作並改變flags值,置入BLOCK_NEEDS_FREE和初始的引用計數 ... } // already copied to heap else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {//當再次拷貝i時,則僅僅增加其引用計數 latching_incr_int(&src->forwarding->flags); } // assign byref data block pointer into new Block _Block_assign(src->forwarding, (void **)destp);//這句僅僅是直接賦值,其函數實現只有一行賦值語句,查閱runtime.c可知 }
所以,我們知道,當我們多次copy一個block時,其引用的__block變量只會被拷貝一次。
2.objc變量的複製
當objc變量沒有__block修飾時:
static void __OBJ1__of2_block_copy_0(struct __OBJ1__of2_block_impl_0*dst, struct __OBJ1__of2_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}void _Block_object_assign(void *destAddr, const void *object, const int flags) { ... else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) { //printf("retaining object at %p\n", object); _Block_retain_object(object);//當我們沒有開啓arc時,這個函數會retian此object //printf("done retaining object at %p\n", object); _Block_assign((void *)object, destAddr); } .... }
當objc變量有__block修飾時:
struct __Block_byref_bSelf_0 { void *__isa; __Block_byref_bSelf_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); OBJ1 *bSelf; }; 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_BYREF_CALLER } static void __Block_byref_id_object_dispose_131(void *src) { _Block_object_dispose(*(void * *) ((char*)src + 40), 131); } ... //33554432即爲BLOCK_HAS_COPY_DISPOSE __block __Block_byref_bSelf_0 bSelf = {(void*)0,(__Block_byref_bSelf_0 *)&bSelf, 33554432, sizeof(__Block_byref_bSelf_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, self};
BLOCK_HAS_COPY_DISPOSE告訴內部實現,這個變量結構體具有自己的copy/dispose輔助函數,而此時我們的內部實現不會進行默認的複製操作:
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); } }
當我們沒有開啓arc,且flags中具有BLOCK_BYREF_CALLER時,會進入_Block_assign函數,而此函數僅僅是賦值
所以,如果要避免objc實例中的block引起的循環引用,我們需要讓block間接使用self:
__block bSelf = self;
其他
對於dipose輔助函數,其行爲與copy是類似的,我們不再重複同樣的東西,如果大家要了解,自行查閱runtime.c和Block_private.h即可。
我們已經理解了非arc非gc情況下的block的內存管理內部實現,對arc和gc的情況,其行爲也是類似的,只是一些函數的指針指向的真正函數會改變,比如_Block_use_GC函數,會將一些函數指向其他的實現,使其適用於gc開啓的情況。
小結
block實際上是一些執行語句和語句需要的上下文的組合,而runtime給予的內部實現決定了它不會浪費一比特的內存。
我們知道cocoa中的容器類class有mutable和immutable之分,實際上我們可以將block看做一個immutable的容器,其盛放的是執行的代碼和執行此代碼需要的變量,而一個immutable變量的無法改變的特質,也決定了block在複製時,的確沒有必要不斷分配新的內存。故而其複製的行爲會是增加引用計數。