block介紹(四)揭開神祕面紗(下)

終於有空開始這系列最後一篇的編寫。 html

這一篇,咱們將看到block的內存管理的內部實現,經過剖析runtime庫源碼,咱們能夠更深入的理解block的內存運做體系。 web

看此篇時,請你們同時打開兩個網址(或者下載它們到本地而後打開): svn

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 spa

 

內存管理的真面目

 

objc層面如何區分不一樣內存區的block

Block_private.h中有這樣一組值: .net

/* 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.棧 code

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,intflags=0) :self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};




在棧上建立的block,其isa指針是_NSConcreteStackBlock。 htm

2.全局區 blog

在全局區建立的block,其比較相似,其構造函數會將isa指針賦值爲_NSConcreteGlobalBlock。

3.堆

咱們沒法直接建立堆上的block,堆上的block須要從stack block拷貝得來,在runtime.c中的_Block_copy_internal函數中,有這樣幾行:

// Its a stack block.  Make a copy.
if(!isGC) {
    structBlock_layout *result = malloc(aBlock->descriptor->size);
    ...
    result->isa = _NSConcreteMallocBlock;
    ...
    returnresult;
}



能夠看到,棧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,從而進行不一樣的操做:

staticvoid*_Block_copy_internal(constvoid*arg,constintflags) {
    ...
    aBlock = (structBlock_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的複製

elseif(aBlock->flags & BLOCK_IS_GLOBAL) {
    returnaBlock;
}





全局block進行copy是直接返回了原block,沒有任何的其餘操做。

 

3.堆block的複製

if(aBlock->flags & BLOCK_NEEDS_FREE) {
    // latches on high
    latching_incr_int(&aBlock->flags);
    returnaBlock;
}



棧block複製時,置入的BLOCK_NEEDS_FREE標記此時起做用,_Block_copy_internal函數識別當前block是一個堆block,則僅僅增長引用計數,而後返回原block。

 

輔助copy/dispose函數

1.普通變量的複製

輔助copy函數用於拷貝block所引用的可修改變量,咱們這裏以 __block int i = 1024爲例:

先看看Block_private.h中的定義:

structBlock_byref {
    void*isa;
    structBlock_byref *forwarding;
    intflags;/* refcount; */
    intsize;
    void(*byref_keep)(structBlock_byref *dst,structBlock_byref *src);
    void(*byref_destroy)(structBlock_byref *);
    /* long shared[0]; */
};



而咱們的__block int i = 1024的轉碼:

struct__Block_byref_i_0 {
  void*__isa;
__Block_byref_i_0 *__forwarding;
 int__flags;
 int__size;
 inti;
};//因此咱們知道,當此結構體被類型強轉爲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爲0



staticvoid__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,constvoid*object,constintflags) {//此處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);
    }
    ...
}
 
staticvoid_Block_byref_assign_copy(void*dest,constvoid*arg,constintflags) {//此處flags爲8,即BLOCK_FIELD_IS_BYREF
    structBlock_byref **destp = (structBlock_byref **)dest;
    structBlock_byref *src = (structBlock_byref *)arg;
    ...
    elseif((src->forwarding->flags & BLOCK_REFCOUNT_MASK) ==0) {//當初次拷貝i時,flags爲0,進入此分支會進行復制操做並改變flags值,置入BLOCK_NEEDS_FREE和初始的引用計數
       ...
    }
    // already copied to heap
    elseif((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修飾時:

staticvoid__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,constvoid*object,constintflags) {
    ...
    elseif((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;
};
staticvoid__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
}
staticvoid__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,constvoid*object,constintflags) {
    //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在複製時,的確沒有必要不斷分配新的內存。故而其複製的行爲會是增長引用計數。

 

最後,參考資料列表以下

http://thirdcog.eu/pwcblocks/#cblocks-memory

http://blog.csdn.net/jasonblog/article/details/7756763

http://clang.llvm.org/docs/Block-ABI-Apple.html

http://www.tanhao.me/pieces/310.html

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

相關文章
相關標籤/搜索