天天看點

Object - C Block 運用2

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

這一篇,我們将看到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,沒有任何的其他操作。

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為0
           
static 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在複制時,的确沒有必要不斷配置設定新的記憶體。故而其複制的行為會是增加引用計數。

最後,參考資料清單如下

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