首先探究下Block的實現(xiàn)原理,由于Objective-C是C語言的超集,既然OC中的NSObject對象其實是由C語言的struct+isa指針實現(xiàn)的,那么Block的內(nèi)部實現(xiàn)估計也一樣,以下三篇Blog對Block的實現(xiàn)機制做了詳細研究:
雖然實現(xiàn)細節(jié)看著頭痛,不過發(fā)現(xiàn)Block果然是和OC中的NSObject類似,也是用struct實現(xiàn)出來的東西。這個是LLVM項目compiler-rt分析的block頭文Block_PRivate.h頭文件中關于Block的struct聲明:
123456789101112131415 |
|
我們發(fā)現(xiàn)Block_layout中也有一個isa指針,像極了NSobject內(nèi)部實現(xiàn)struct中的isa指針。這里的isa可能指向三種類型之一的Block:
為什么會有這么多種類呢?首先來看全局類型Block,看例子:
123456789101112 |
|
為什么addBlock中添加到array中的Block屬于全局Block呢?因為它不需要運行時(Runtime)任何的狀態(tài)來改變行為,不需要放在堆上或者棧上,直接編譯后在代碼段中即可,就像個c函數(shù)一樣。這種類型的Block在ARC和non-ARC情況下沒有差別。
這個Block訪問了作用域外的變量d,在實現(xiàn)上就是這個block會多一個成員變量對應這個d,在賦值block時會將方法exmpale中的d變量值復制到成員變量中,從而實現(xiàn)訪問。
1234567 |
|
如果要修改d呢?:
123456789 |
|
由于局部變量d和這個block的實現(xiàn)不在同一作用域,僅僅在調(diào)用過程中用到了值傳遞,所以不能直接修改,而需要加一個標識符__block int d = 5;
,那么block就可以實現(xiàn)對這個局部變量的修改了。如果是這種block標識的變量,在Block實現(xiàn)中不再是簡單的一個成員變量,而是對應一個新的結構體表示這個block變量。block的本質(zhì)是引入了一個新的Block_byref{$var_name}{$index}結構體,被block關鍵字修飾的變量就被放到這個結構體中。另外,block結構體通過引入Block_byref{$var_name}{$index}指針類型的成員,得以間接訪問到Block的外部變量。這樣對Block外的變量訪問從值傳遞轉變?yōu)橐茫瑥亩辛诵薷膬?nèi)容的能力。
正常我們使用Block是在棧上生成的,離開了棧作用域便釋放了,如果copy一個Block,那么會將這個Block copy到堆上分配,這樣就不再受棧的限制,可以隨意使用啦。例如:
1234567891011121314 |
|
函數(shù)getBlock中聲明并賦值的returnedBlock,一開始是在棧上分配的,屬于NSStackBlock,如果是non-ARC情況下return這個NSStackBlock,那么其實已經(jīng)被銷毀了,在函數(shù)中example()使用時就會crash。如果是ARC情況下,getBlock返回的block會自動copy到堆上,那么block的類型就是NSMallocBlock,可以在example()中繼續(xù)使用。要在Non-ARC情況下正常運行,那么就應該修改為:
1234567 |
|
扯了這么多,回到Block的循環(huán)引用問題,由于我們很多行為會導致Block的copy,而當Block被copy時,會對block中用到的對象產(chǎn)生強引用(ARC下)或者引用計數(shù)加一(non-ARC下)。
如果遇到這種情況:
123456789 |
|
對象有一個Block屬性,然而這個Block屬性中又引用了對象的其他成員變量,那么就會對這個變量本身產(chǎn)生強應用,那么變量本身和他自己的Block屬性就形成了循環(huán)引用。在ARC下需要修改成這樣:
123456789 |
|
也就是生成一個對自身對象的弱引用,如果是倒霉催的項目還需要支持iOS4.3,就用__unsafe_unretained替代__weak。如果是non-ARC環(huán)境下就將__weak替換為__block即可。non-ARC情況下,__block變量的含義是在Block中引入一個新的結構體成員變量指向這個__block變量,那么__block typeof(self) weakSelf = self;
就表示Block別再對self對象retain啦,這就打破了循環(huán)引用。
新聞熱點
疑難解答