Block中__block實(shí)現(xiàn)原理的探究之路

讓我們一起研究一下__block實(shí)現(xiàn)原理。

1.普通非對(duì)象的變量

先來(lái)看看普通變量的情況。

把上述代碼用clang轉(zhuǎn)換成源碼。


從源碼我們能發(fā)現(xiàn),帶有 __block的變量也被轉(zhuǎn)化成了一個(gè)結(jié)構(gòu)體__Block_byref_i_0,這個(gè)結(jié)構(gòu)體有5個(gè)成員變量。第一個(gè)是isa指針,第二個(gè)是指向自身類(lèi)型的__forwarding指針,第三個(gè)是一個(gè)標(biāo)記flag,第四個(gè)是它的大小,第五個(gè)是變量值,名字和變量名同名。

源碼中是這樣初始化的。__forwarding指針初始化傳遞的是自己的地址。然而這里__forwarding指針真的永遠(yuǎn)指向自己么?我們來(lái)做一個(gè)實(shí)驗(yàn)。


我們把Block拷貝到了堆上,這個(gè)時(shí)候打印出來(lái)的2個(gè)i變量的地址就不同了。


地址不同就可以很明顯的說(shuō)明__forwarding指針并沒(méi)有指向之前的自己了。那__forwarding指針現(xiàn)在指向到哪里了呢?

Block里面的__block的地址和Block的地址就相差1052。我們可以很大膽的猜想,__block現(xiàn)在也在堆上了。

出現(xiàn)這個(gè)不同的原因在于這里把Block拷貝到了堆上。

由第二章里面詳細(xì)分析的,堆上的Block會(huì)持有對(duì)象。我們把Block通過(guò)copy到了堆上,堆上也會(huì)重新復(fù)制一份Block,并且該Block也會(huì)繼續(xù)持有該__block。當(dāng)Block釋放的時(shí)候,__block沒(méi)有被任何對(duì)象引用,也會(huì)被釋放銷(xiāo)毀。

__forwarding指針這里的作用就是針對(duì)堆的Block,把原來(lái)__forwarding指針指向自己,換成指向_NSConcreteMallocBlock上復(fù)制之后的__block自己。然后堆上的變量的__forwarding再指向自己。這樣不管__block怎么復(fù)制到堆上,還是在棧上,都可以通過(guò)(i->__forwarding->i)來(lái)訪問(wèn)到變量值。

所以在__main_block_func_0函數(shù)里面就是寫(xiě)的(i->__forwarding->i)。這里還有一個(gè)需要注意的地方。還是從例子說(shuō)起:


結(jié)果和之前copy的例子完全不同。

Block在捕獲住__block變量之后,并不會(huì)復(fù)制到堆上,所以地址也一直都在棧上。這與ARC環(huán)境下的不一樣。

ARC環(huán)境下,不管有沒(méi)有copy,__block都會(huì)變copy到堆上,Block也是__NSMallocBlock。

感謝@酷酷的哀殿 指出錯(cuò)誤,感謝@bestswifter 指點(diǎn)。上述說(shuō)法有點(diǎn)不妥,詳細(xì)見(jiàn)文章末尾更新。

ARC環(huán)境下,一旦Block賦值就會(huì)觸發(fā)copy,__block就會(huì)copy到堆上,Block也是__NSMallocBlock。ARC環(huán)境下也是存在__NSStackBlock的時(shí)候,這種情況下,__block就在棧上。

MRC環(huán)境下,只有copy,__block才會(huì)被復(fù)制到堆上,否則,__block一直都在棧上,block也只是__NSStackBlock,這個(gè)時(shí)候__forwarding指針就只指向自己了。


至此,__block的實(shí)現(xiàn)原理也已經(jīng)明了。

2.對(duì)象的變量

還是先舉一個(gè)例子:

輸出

我們把上面的代碼轉(zhuǎn)換成源碼研究一下:

首先需要說(shuō)明的一點(diǎn)是對(duì)象在OC中,默認(rèn)聲明自帶__strong所有權(quán)修飾符的,所以main開(kāi)頭我們聲明的

等價(jià)

在轉(zhuǎn)換出來(lái)的源碼中,我們也可以看到,Block捕獲了__block,并且強(qiáng)引用了,因?yàn)樵赺_Block_byref_block_obj_0結(jié)構(gòu)體中,有一個(gè)變量是id block_obj,這個(gè)默認(rèn)也是帶__strong所有權(quán)修飾符的。

根據(jù)打印出來(lái)的結(jié)果來(lái)看,ARC環(huán)境下,Block捕獲外部對(duì)象變量,是都會(huì)copy一份的,地址都不同。只不過(guò)帶有__block修飾符的變量會(huì)被捕獲到Block內(nèi)部持有。

我們?cè)賮?lái)看看MRC環(huán)境下的情況,還是將上述代碼的例子運(yùn)行在MRC中。

輸出:

這個(gè)時(shí)候block在棧上,__NSStackBlock__,可以打印出來(lái)retainCount值都是1。當(dāng)把這個(gè)block copy一下,就變成__NSMallocBlock__,對(duì)象的retainCount值就會(huì)變成2了。

總結(jié):

在MRC環(huán)境下,__block根本不會(huì)對(duì)指針?biāo)赶虻膶?duì)象執(zhí)行copy操作,而只是把指針進(jìn)行的復(fù)制。

而在ARC環(huán)境下,對(duì)于聲明為_(kāi)_block的外部對(duì)象,在block內(nèi)部會(huì)進(jìn)行retain,以至于在block環(huán)境內(nèi)能安全的引用外部對(duì)象,所以才會(huì)產(chǎn)生循環(huán)引用的問(wèn)題!

關(guān)于Block捕獲外部變量有很多用途,用途也很廣,只有弄清了捕獲變量和持有的變量的概念以后,之后才能清楚的解決Block循環(huán)引用的問(wèn)題。

再次回到文章開(kāi)頭,5種變量,自動(dòng)變量,函數(shù)參數(shù) ,靜態(tài)變量,靜態(tài)全局變量,全局變量,如果嚴(yán)格的來(lái)說(shuō),捕獲是必須在Block結(jié)構(gòu)體__main_block_impl_0里面有成員變量的話,Block能捕獲的變量就只有帶有自動(dòng)變量和靜態(tài)變量了。捕獲進(jìn)Block的對(duì)象會(huì)被Block持有。

對(duì)于非對(duì)象的變量來(lái)說(shuō),

自動(dòng)變量的值,被copy進(jìn)了Block,不帶__block的自動(dòng)變量只能在里面被訪問(wèn),并不能改變值。

帶__block的自動(dòng)變量 和 靜態(tài)變量 就是直接地址訪問(wèn)。所以在Block里面可以直接改變變量的值。

而剩下的靜態(tài)全局變量,全局變量,函數(shù)參數(shù),也是可以在直接在Block中改變變量值的,但是他們并沒(méi)有變成Block結(jié)構(gòu)體__main_block_impl_0的成員變量,因?yàn)樗麄兊淖饔糜虼?,所以可以直接更改他們的值?/p>

值得注意的是,靜態(tài)全局變量,全局變量,函數(shù)參數(shù)他們并不會(huì)被Block持有,也就是說(shuō)不會(huì)增加retainCount值。

對(duì)于對(duì)象來(lái)說(shuō),

在MRC環(huán)境下,__block根本不會(huì)對(duì)指針?biāo)赶虻膶?duì)象執(zhí)行copy操作,而只是把指針進(jìn)行的復(fù)制。

而在ARC環(huán)境下,對(duì)于聲明為_(kāi)_block的外部對(duì)象,在block內(nèi)部會(huì)進(jìn)行retain,以至于在block環(huán)境內(nèi)能安全的引用外部對(duì)象。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容