在第一篇文章和第二篇文章咱們已經研究了一些blocks的內部原理了。本文將進一步研究block拷貝的過程。你可能聽到過一些術語好比"blocks 起始於棧"以及"若是想保存它們之後用你必須拷貝"。可是爲何呢?拷貝到底作了什麼事?我長久以來一直在好奇拷貝block的機制究竟是什麼。好比block捕獲的值會怎麼樣。本文我將對此作些闡述。程序員
從第一篇文章和第二篇文章中咱們知道一個block的內存佈局長這樣:bash
在第二篇文章中咱們看到block最開始被引用的時候是在棧上建立的。既然是在棧上,那麼在block的封閉域結束後內存就會被回收重用。那你以後再用這個block會發生什麼呢?好吧,你必須拷貝它。這是經過調用Block_copy()
方法或者直接向他發送OC的copy
消息完成。這就是所謂的Block_copy()
。數據結構
首先咱們來看Block.h。其中有下面的定義:app
#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
void *_Block_copy(const void *arg);複製代碼
因此Block_copy
是一個宏,它將傳入的參數轉換爲一個const void *
而後傳遞給_Block_copy()
方法。_Block_copy()
的實如今runtime.c:函數
void *_Block_copy(const void *arg) {
return _Block_copy_internal(arg, WANTS_ONE);
}複製代碼
因此也就是調用_Block_copy_internal
方法,傳入block本身和WANTS_ONE
。爲了明白這什麼意思,咱們須要看一下實現代碼。也在runtime.c。下面是方法的實現,已經刪掉不想幹的部分(主要是垃圾收集的部分):佈局
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
// 1
if (!arg) return NULL;
// 2
aBlock = (struct Block_layout *)arg;
// 3
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 4
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// 5
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 6
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// 7
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 8
result->isa = _NSConcreteMallocBlock;
// 9
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}複製代碼
主要作了如下工做:ui
若是傳入參數是NULL
就直接返回NULL
。防止傳入一個NULL
的Block。spa
將參數轉換爲一個struct Block_layout
類型的指針。你也許還記得第一篇文章中提到它。它就是block內部一個包含了實現函數和一些元數據的數據結構。指針
若是block的flags
字段包含BLOCK_NEEDS_FREE
,那麼這是一個堆block(稍後你就明白)。這裏只須要增長引用計數而後返回原blcok。code
若是這是一個全局block(回看第一篇文章),那麼不須要作任何事,直接返回原block。由於全局block是一個單例。
若是走到這裏,那麼這必定是一個棧上分配的block。那樣的話,block須要拷貝到堆上。這纔是有趣的部分。第一步,調用malloc()
建立一塊特定的內存。若是建立失敗,返回NULL
;不然,繼續。
調用memmove()
方法將當前棧上分配的block按位拷貝到咱們剛剛建立的堆內存上。這樣能夠保證全部的元數據都拷貝過來,好比descriptor。
接下來,更新標誌位。第一行確保引用計數爲0。註釋代表這行其實不須要——大概這個時候引用計數已是0了。我猜保留這行是由於之前有個bug致使這裏的引用計數不是0(因此說runtime的代碼也會偷懶)。下一行設置了BLOCK_NEEDS_FREE
標誌位,代表這是一個堆block,一旦引用計數減爲0,它所佔用的內存將被釋放。|1
操做設置block的引用計數爲1。
block的isa
指針被設置爲_NSConcreteMallocBlock
,說明這是個堆block。
最後,若是block有一個拷貝輔助函數,那麼它將被調用。必要的時候編譯器會生成拷貝輔助函數。好比一個捕獲了對象的block就須要。那麼拷貝輔助函數將持有被捕獲的對象。
哈哈,已經十分清晰了。如今你知道拷貝一個block到底發生了什麼事!但那只是圖片展現的一半內容,對吧?釋放一個block又會怎麼樣呢?
Block_copy()
圖的另外一半是Block_release()
。實際上這又是一個宏:
#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))複製代碼
跟Block_copy()
同樣,Block_release()
也是轉換傳入的參數而後調用一個方法。這必定程度上解放了程序員的雙手,他們不用本身作轉換。
咱們來看看_Block_release()
的源碼(簡明起見,從新整理了代碼順序,並刪除了垃圾回收相關的代碼):
```void _Block_release(void arg) {
// 1
struct Block_layout aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
// 2
int32_t newCount;
newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
// 3
if (newCount > 0) return;
// 4
if (aBlock->flags & BLOCK_NEEDS_FREE) {
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
_Block_deallocator(aBlock);
}
// 5
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
;
}
// 6
else {
printf("Block_release called upon a stack Block: %p, ignored\n", (void *)aBlock);
}複製代碼
}
```
這段代碼作了這些事:
首先,參數被轉換爲一個指向struct Block_layout
的指針。若是傳入NULL,直接返回。
標誌位部分表示引用計數減1(以前Block_copy()
中標誌位操做表明的是引用計數置爲1)。
若是新的引用計數值大於0,說明有其餘東西在引用block,因此block不該該被釋放。
不然,若是標誌位包含BLOCK_NEEDS_FREE
,那麼這是一個堆block並且引用計數爲0,應該被釋放。首先block的處理輔助函數(dispose helper)被調用,它是拷貝輔助函數(copy helper)的反義詞,執行相反的操做,好比釋放被捕獲的對象。最後調用_Block_deallocator
方法釋放block。若是你查找runtime.c你就會發現這個方法最後就是一個free
的函數指針,釋放malloc
分配的內存。
若是到這一步且lock是全局的,什麼也不作。
若是到這一步,必定是發生了未知情況,由於一個棧block試圖在這裏釋放,輸出一行警告。實際上,你應該永遠不會走到這一步。
這些就是Block! 東西也並很少嘛(呵呵)。