深度圍觀block 三

2021-06-20 14:12:11 字數 4461 閱讀 7957

blocks1

本文由破船

譯自galloway

本文是深度圍觀block的第三篇文章,也是最後一篇。希望讀者閱讀了之後,對block有更加深入的理解,同時也希望之前對組合語言恐懼或者陌生的讀者轉變看法,其實只要你用心去看,去學,很容易就搞懂的。

。本文話費了很長時間才出爐。實際上,幾個月之前就已經打好草稿了,只不過一直忙於寫我的這本書:effective objective-c 2.0

,所以沒有時間完成本文。

接著之前的兩篇文章:深度圍觀block:第一集

和深度圍觀block:第二集

,本文將更進一步了解當block被拷貝時發生了什麼。可能你已經聽過這樣的說辭「block開始於棧」,以及「如果你希望將block儲存下來,以便後續使用,那麼必須對block進行拷貝」。那麼,這是為什麼呢?而在拷貝過程中實際又會發生什麼情況?我一直在思考拷貝block時是利用了什麼機制。就如之前介紹的block在進行值拷貝時發生了什麼。本文我將揭曉這些疑問。

通過第一集

和第二集

block_layout

在第二集

中,我們也知道了當block初始化的時候,會在棧中建立像上圖這樣的乙個結構。由於這個結構是在棧上,而在棧空間是會被重複使用的。那麼如果我們想要在以後繼續使用該block,就必須要對block進行拷貝操作。拷貝操作需要呼叫block_copy()

函式,或者可以理解為給block傳送乙個copy

訊息(因為block可以看成乙個objective-c物件),這也會呼叫block_copy()

函式。下面我們就來看看block_copy()函式都做了什麼。

我們首先來看看block.h

檔案,在這裡面可以看到如下定義:

#define block_copy(...) ((__typeof(__va_args__))_block_copy((const void *)(__va_args__)))

void *_block_copy(const void *arg);

可以看出,block_copy()

實際上就是乙個巨集定義(#define

),該巨集定義將傳入的引數(const void *

)做強制型別轉換,然後再傳給_block_copy()

。我們也可以在實現檔案runtime.c

中找到_block_copy()

的原型:

void *_block_copy(const void *arg)
上面的方法呼叫了_block_copy_internal()

函式,並傳入block本身(arg)以及wants_one

。要弄白具體意思,需要檢視_block_copy_internal方法的實現,該方法也是在runtime.c

static void *_block_copy_internal(const void *arg, const int flags) 

// 4

else if (ablock->flags & block_is_global)

// 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); // *** not needed

result->flags |= block_needs_free | 1;

// 8

result->isa = _nsconcretemallocblock;

// 9

if (result->flags & block_has_copy_dispose)

return result;

}

下面來看看該方法都做了些什麼事情:

1、如果傳入的引數是null

則直接返回null

。這樣可以保證傳入乙個null

block時函式的安全性。

2、將引數強制轉換為乙個指標,該指標指向乙個block_layout

結構物件。實際上在第一集

中就介紹了block_layout結構:這是乙個內部使用的資料結構,該結構組成乙個block,其中包含乙個block的實現函式,以及另外幾個元資料。

3、 如果block的flags包含block_needs_free,說明這是乙個堆block(a heap block)。這種情況下,需要做的事情就是增加引用計數(reference count),然後將同乙個的block返回。

4、如果block是乙個全域性block(參考第一集

),那麼不用做任何事情,直接返回同乙個block即可——因為全域性block是乙個單例(singleton)。

5、如果到這一步了,可以肯定該block肯定被分配在棧上。這種情況,需要將block拷貝到堆上。這也是最有趣的一部分。首先是利用malloc()函式在堆上建立block對應size大小的記憶體空間。如果失敗了,就返回null

,否則繼續往下執行。

6、 利用memmove()

函式將分配在棧中的block按位拷貝至剛剛在堆上分配的空間中。按位拷貝可以確保block中的所有元資料都能準確的進行拷貝,例如block的descriptor。

7、接著需要更新一下block的flags。第一行**是確保引用計數被設定為0。後面緊跟的注釋表示這不是必須的——估計此時引用計數已經是0了。我猜測這行**的作用是為了防止潛在的bug,會引起引用計數不為0的情況。第二行**是設定block_needs_free

標誌,這標示該block是乙個堆block,當引用計數變為0時,需要free

掉。後面緊跟的| 1

是將block的引用計數設定為1。

8、將block的isa

指標設定為 _nsconcretemallocblock,這就意味著該block是乙個堆block。

9、最後,如果block有乙個拷貝輔助函式(a copy helper function),那麼就呼叫它。如果有必要的話,表一起會生成乙個拷貝輔助函式。例如block需要拷貝物件的時候,拷貝輔助函式會retain住已經拷貝的物件。

思路很清晰吧!現在你應該知道當block被拷貝時會發什麼了!下面還需要了解一下當release時又回發生什麼?

與block_copy對應的是block_release()。同樣,block_release()也是乙個巨集定義,如下所示:

#define block_release(...) _block_release((const void *)(__va_args__))
實際上,跟block_copy()

類似,block_release()

會為我們把引數進行強制型別轉換。這樣開發者就不用親自來處理轉換的事情了。

下面我們來看看_block_release()

void _block_release(void *arg) 

// 5

else if (ablock->flags & block_is_global)

// 6

else

}

來看看他們都做了些什麼:

1、 首先將引數強制轉換為block_layout

結構。如果傳入的是null

,那麼為了函式的安全起見,將直接返回。

2、將block的引用計數標誌位減1(還記得block_copy()

中將這個引用計數標誌位設定為1嗎?)。

3、如果newcount大於0,說明還有別的物件引用了這個block,所以並不需要立即釋放block,只需簡單的返回即可。

4、否則,如果flags中包含block_needs_free

,那麼說明這個block是分配到堆上的,並且如果引用計數為0,那麼需要釋放這個block。首先是呼叫了block的dispose輔助函式,該函式跟copy輔助函式相反,負責做相反的操作,例如釋放掉所有在block中拷貝的變數等。最後使用_block_deallocator

函式釋放掉block,如果你去runtime.c

檔案中看看,會發現該函式的尾部是乙個指向free

的函式指標,也就是釋放掉malloc

分配的記憶體。

5、如果block是全域性的,那麼什麼事情也不用做。

6、如果**執行到這裡了,會發生一些奇怪的事情:因為正在嘗試將棧上的block釋放掉,所以這行**是為了提醒開發者的。在程式實際執行過程中,永遠不會看到這裡的提示。

coool!就是這些了,沒有更多,也沒有再複雜的東西了!

本文也是我深度圍觀block的最後一篇。其中有一些內容也可也在我的這本書中找到:effective objective-c 2.0

。這一系列文章介紹了如何有效的使用block,並且如果你對block感興趣的話,這系列的內容也可以幫助你更加深入的了解block。

本文由破船

ios學習 block深度解析

1.block的本質是乙個objective c的物件,為什麼這麼說?在objective c中,runtime會在執行時根據物件的isa指標的指向,來度額定這個物件的型別,也可以認為乙個物件,它具有isa指標,就是乙個oc物件 2.你怎麼知道block有isa指標呢,我們可以通過clang命令將來...

圍觀網路之三 淺探索NDIS5 1 1

前言 本文討論w2k xp適用的ndis5.x 網路架構。ndis4.0原始碼太老,ros又yy了太多,所以這次的參考 基本都是自己f5的 具體結構都有了,我f5的毫無壓力 除錯的時候利用imd 中間層驅動 下斷點,更無壓力了.ndis5.x網路的堆疊結構大概是 winsock api afd 協議...

深度學習(三)

1 在調整超引數時,其中最終要的是 其次是動量 隱藏層單元以及mini batch,之後還可以調整層數跟學習率衰減 2 在深度學習中,隨機選擇引數的值可能更好一些,因為事先並不知道哪個引數相對比較重要,另乙個比較重要的原則是從粗略到精細,到大體確定了超引數的範圍,再在這個範圍內進行隨機取值,獲得更加...