iOS中block介紹(四)揭開神秘面紗(下)
看此篇時(shí),請(qǐng)大家同時(shí)打開兩個(gè)網(wǎng)址(或者下載它們到本地然后打開):
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
內(nèi)存管理的真面目
objc層面如何區(qū)分不同內(nèi)存區(qū)的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];
其用于對(duì)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;
- }
- };
在棧上創(chuàng)建的block,其isa指針是_NSConcreteStackBlock。
2.全局區(qū)
在全局區(qū)創(chuàng)建的block,其比較類似,其構(gòu)造函數(shù)會(huì)將isa指針賦值為_NSConcreteGlobalBlock。
3.堆
我們無法直接創(chuàng)建堆上的block,堆上的block需要從stack block拷貝得來,在runtime.c中的_Block_copy_internal函數(shù)中,有這樣幾行:
- // Its a stack block. Make a copy.
- if (!isGC) {
- struct Block_layout *result = malloc(aBlock->descriptor->size);
- ...
- result->isa = _NSConcreteMallocBlock;
- ...
- return result;
- }
可以看到,棧block復(fù)制得來的新block,其isa指針會(huì)被賦值為_NSConcreteMallocBlock
4.其余的isa類型
- BLOCK_EXPORT void * _NSConcreteAutoBlock[32];
- BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32];
- BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
其他三種類型是用于gc和arc,我們暫不討論
復(fù)制block
對(duì)block調(diào)用Block_copy方法,或者向其發(fā)送objc copy消息,最終都會(huì)調(diào)用runtime.c中的_Block_copy_internal函數(shù),其內(nèi)部實(shí)現(xiàn)會(huì)檢查block的flag,從而進(jìn)行不同的操作:
- static void *_Block_copy_internal(const void *arg, const int flags) {
- ...
- aBlock = (struct Block_layout *)arg;
- ...
- }1.棧block的復(fù)制
- // 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指針的值之外,拷貝過程中,還會(huì)將BLOCK_NEEDS_FREE置入,大家記住這個(gè)值,后面會(huì)用到。
***,如果block有輔助copy/dispose函數(shù),那么輔助的copy函數(shù)會(huì)被調(diào)用。
2.全局block的復(fù)制
- else if (aBlock->flags & BLOCK_IS_GLOBAL) {
- return aBlock;
- }全局block進(jìn)行copy是直接返回了原block,沒有任何的其他操作。
全局block進(jìn)行copy是直接返回了原block,沒有任何的其他操作。
3.堆block的復(fù)制
- if (aBlock->flags & BLOCK_NEEDS_FREE) {
- // latches on high
- latching_incr_int(&aBlock->flags);
- return aBlock;
- }
棧block復(fù)制時(shí),置入的BLOCK_NEEDS_FREE標(biāo)記此時(shí)起作用,_Block_copy_internal函數(shù)識(shí)別當(dāng)前block是一個(gè)堆block,則僅僅增加引用計(jì)數(shù),然后返回原block。
輔助copy/dispose函數(shù)
1.普通變量的復(fù)制
輔助copy函數(shù)用于拷貝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的轉(zhuǎn)碼:
- struct __Block_byref_i_0 {
- void *__isa;
- __Block_byref_i_0 *__forwarding;
- int __flags;
- int __size;
- int i;
- };//所以我們知道,當(dāng)此結(jié)構(gòu)體被類型強(qiáng)轉(zhuǎn)為Block_byref時(shí),前四個(gè)成員是一致的,訪問flags就相當(dāng)于訪問__flags,而內(nèi)部實(shí)現(xiàn)就是這樣使用的
- ...
- __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024};//i初始化時(shí)__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*/);}
此時(shí),復(fù)制時(shí)調(diào)用的輔助函數(shù):
- 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) {//當(dāng)初次拷貝i時(shí),flags為0,進(jìn)入此分支會(huì)進(jìn)行復(fù)制操作并改變flags值,置入BLOCK_NEEDS_FREE和初始的引用計(jì)數(shù)
- ...
- }
- // already copied to heap
- else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {//當(dāng)再次拷貝i時(shí),則僅僅增加其引用計(jì)數(shù)
- latching_incr_int(&src->forwarding->flags);
- }
- // assign byref data block pointer into new Block
- _Block_assign(src->forwarding, (void **)destp);//這句僅僅是直接賦值,其函數(shù)實(shí)現(xiàn)只有一行賦值語句,查閱runtime.c可知
- }
所以,我們知道,當(dāng)我們多次copy一個(gè)block時(shí),其引用的__block變量只會(huì)被拷貝一次。
2.objc變量的復(fù)制
當(dāng)objc變量沒有__block修飾時(shí):
- 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);//當(dāng)我們沒有開啟arc時(shí),這個(gè)函數(shù)會(huì)retian此object
- //printf("done retaining object at %p\n", object);
- _Block_assign((void *)object, destAddr);
- }
- ....
- }
當(dāng)objc變量有__block修飾時(shí):
- 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告訴內(nèi)部實(shí)現(xiàn),這個(gè)變量結(jié)構(gòu)體具有自己的copy/dispose輔助函數(shù),而此時(shí)我們的內(nèi)部實(shí)現(xiàn)不會(huì)進(jìn)行默認(rèn)的復(fù)制操作:
- 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);
- }
- }
當(dāng)我們沒有開啟arc,且flags中具有BLOCK_BYREF_CALLER時(shí),會(huì)進(jìn)入_Block_assign函數(shù),而此函數(shù)僅僅是賦值
所以,如果要避免objc實(shí)例中的block引起的循環(huán)引用,我們需要讓block間接使用self:
__block bSelf = self;
其他
對(duì)于dipose輔助函數(shù),其行為與copy是類似的,我們不再重復(fù)同樣的東西,如果大家要了解,自行查閱runtime.c和Block_private.h即可。
我們已經(jīng)理解了非arc非gc情況下的block的內(nèi)存管理內(nèi)部實(shí)現(xiàn),對(duì)arc和gc的情況,其行為也是類似的,只是一些函數(shù)的指針指向的真正函數(shù)會(huì)改變,比如_Block_use_GC函數(shù),會(huì)將一些函數(shù)指向其他的實(shí)現(xiàn),使其適用于gc開啟的情況。
小結(jié)
block實(shí)際上是一些執(zhí)行語句和語句需要的上下文的組合,而runtime給予的內(nèi)部實(shí)現(xiàn)決定了它不會(huì)浪費(fèi)一比特的內(nèi)存。
我們知道cocoa中的容器類class有mutable和immutable之分,實(shí)際上我們可以將block看做一個(gè)immutable的容器,其盛放的是執(zhí)行的代碼和執(zhí)行此代碼需要的變量,而一個(gè)immutable變量的無法改變的特質(zhì),也決定了block在復(fù)制時(shí),的確沒有必要不斷分配新的內(nèi)存。故而其復(fù)制的行為會(huì)是增加引用計(jì)數(shù)。