Block由淺入深及其底層原理.

想來(lái)作為iOS開發(fā)者,一定對(duì)于block不那么陌生,block是蘋果對(duì)于C的一個(gè)拓展,是匿名函數(shù).我們通過(guò)對(duì)代碼塊的封裝可以作為方法參數(shù)實(shí)現(xiàn)網(wǎng)絡(luò)異步請(qǐng)求,也可以作為方法返回值進(jìn)行鏈?zhǔn)骄幊?使用block代替代理等等...

那么這么神奇的一個(gè)Block,在它身上到底會(huì)發(fā)生什么事呢?先讓我們簡(jiǎn)單的認(rèn)識(shí)它:

一.Block的簡(jiǎn)單使用:

1.使用block進(jìn)行簡(jiǎn)單的傳值:(異步回傳)

這里是登錄一個(gè)請(qǐng)求,我在ViewModel里面做了請(qǐng)求,需要把返回的登錄數(shù)據(jù)返回VC,告知它我這里登錄成功,那么我們就可以采用這種Block進(jìn)行回傳.(這里只是為了演示,實(shí)際開發(fā)不建議這么做,在MVVM中,最恰當(dāng)?shù)姆绞绞潜O(jiān)聽VM的屬性再進(jìn)行頁(yè)面操作)

登錄請(qǐng)求

接下來(lái)的事情就很簡(jiǎn)單了,我們對(duì)Block進(jìn)行賦值,來(lái)獲取數(shù)據(jù).(這里的dataReturn在VM初始化就進(jìn)行賦值)


回傳后VC進(jìn)行登錄后的操作

2.將Block作為方法返回值進(jìn)行鏈?zhǔn)骄幊?/h4>

比如我在Label分類中創(chuàng)建一個(gè)StyleConfig類提供項(xiàng)目Label的大體樣式,再在分類中通過(guò)runtime引出這個(gè)屬性,那么我就可以通過(guò)block枚舉傳參的形式來(lái)迅速設(shè)置label的樣式.

這是Config類,通過(guò)Block來(lái)設(shè)置樣式

我們?cè)诜诸愔衼?lái)調(diào)用分類的樣式,通過(guò)Block的參數(shù)枚舉來(lái)判斷該使用哪個(gè)樣式

Block里返回自身,即可通過(guò)get方法來(lái)進(jìn)行點(diǎn)語(yǔ)法鏈?zhǔn)讲僮?/div>

讓我們來(lái)看看如何使用Block來(lái)鏈?zhǔn)皆O(shè)置UI

這樣添加一個(gè)Label就非常簡(jiǎn)潔了

二.Block的循環(huán)引用問(wèn)題:

其實(shí)關(guān)于這個(gè)問(wèn)題,網(wǎng)上已經(jīng)有很多文檔去說(shuō)明了,這里還是簡(jiǎn)單介紹下解決Block循環(huán)引用的幾種方法:

1.引起循環(huán)引用的原因:

VC--強(qiáng)持有-->block--強(qiáng)持有-->VC

這里值得一提的是此時(shí)這個(gè)block不管是否被調(diào)用,都會(huì)造成內(nèi)存無(wú)法釋放.因?yàn)閎lock此時(shí)已經(jīng)捕獲了外界的__strong對(duì)象.

其實(shí)這里已經(jīng)給出了retain cycle警告

2.循環(huán)引用解決方案

a.使用__weak __strong的組合技來(lái)解決,簡(jiǎn)單說(shuō)一下 這里的__strong再次強(qiáng)引用是為了防止vc的提前釋放而導(dǎo)致想要使用的vc為空.因?yàn)榇藭r(shí)強(qiáng)引用的vc在block內(nèi)部的生命周期已經(jīng)被block管理,只在block內(nèi)部的作用域之內(nèi),所以此時(shí)出了block,這里的vc還是會(huì)被正常釋放,不會(huì)造成內(nèi)存泄漏.

或者也可以用typeof(self)來(lái)獲得vc的類型

b.使用__block來(lái)解決循環(huán)引用

值得一提的是,如果要采用__block來(lái)解決循環(huán)引用問(wèn)題的話,這里需要在block里面當(dāng)不需要使用vc的引用屬性的時(shí)候要在生命作用域里手動(dòng)將其置空,并至少保證要將其調(diào)用一次.

為什么__block能解決這里的循環(huán)引用問(wèn)題呢?容我賣一個(gè)關(guān)子,等我后續(xù)解答.

為啥這里就能解決循環(huán)引用呢?

c.將vc作為參數(shù)傳入block

前面有提過(guò)block也是匿名函數(shù),如果將vc作為形參傳入block中 那么其vc指針的生命周期就只在block內(nèi)部,當(dāng)block執(zhí)行完畢其指針就會(huì)被釋放,也就是說(shuō),其引用次數(shù)也會(huì)被-1,block持有vc的引用鏈就會(huì)被斷掉,也就不存在循環(huán)引用問(wèn)題了.

此時(shí)的vc是block的形參,其生命周期將只在block內(nèi).

三.Block的三種類型:

在OC中,block有三種類型:

__NSStackBlock(棧block)

__NSGlobalBlock(全局block)

__NSMallocBlock(堆block)

1.__NSStackBlock:

棧Block在MRC中,如果block有捕獲到外界變量,此時(shí)的block為棧block.

此時(shí)有捕獲到外界變量a

2.__NSMallocBlock:

如果這個(gè)棧區(qū)的block被copy,此時(shí)這個(gè)block就是MallocBlock(堆block)

這個(gè)棧block已經(jīng)被copy到堆中

值得注意的是如果在ARC中,此時(shí)只要捕獲外界變量,那么就直接是MallocBlock,因?yàn)锳RC下會(huì)自動(dòng)將這個(gè)棧block copy到堆中.

ARC下會(huì)自動(dòng)copy到堆

3.__NSGlobalBlock:

如果記述全局變量的地方有聲明block,且這個(gè)block不使用捕獲的外界變量或者根本沒(méi)有捕獲外界變量,此時(shí)的block為__NSGlobalBlock.

這個(gè)block沒(méi)有捕獲外界變量

四.Block的底層剖析:

我們先在.m文件中寫一個(gè)block:

我們將oc的.m文件用clang -rewrite-objc 命令將其轉(zhuǎn)成c++語(yǔ)言,來(lái)揭開它的神秘面紗:

此時(shí)會(huì)生成.cpp文件

搜索__block_impl 可以看到這里:

這里就是存儲(chǔ)block信息的結(jié)構(gòu)體

可以看到block其實(shí)也是個(gè)結(jié)構(gòu)體!

注意這個(gè)void *isa!!!!!! 這個(gè)isa說(shuō)明它實(shí)質(zhì)上也是個(gè)對(duì)象!

我們?cè)俅嗡阉鬟@個(gè)結(jié)構(gòu)體找到對(duì)其賦值的地方看下他們依次的作用

這里對(duì)其進(jìn)行賦值

1.isa:

可以發(fā)現(xiàn)這個(gè)結(jié)構(gòu)體其實(shí)存儲(chǔ)的是block的信息,它的isa指向了_NSConcreteStackBlock,說(shuō)明它是個(gè)棧block.

2.funcPtr:

funcPtr指向我們執(zhí)行這個(gè)block所調(diào)用的函數(shù)實(shí)現(xiàn).

3.flags:

這個(gè)flags就是標(biāo)記這個(gè)block此時(shí)的狀態(tài),因?yàn)镃語(yǔ)言是靜態(tài)語(yǔ)言,無(wú)法獲取block此時(shí)的狀態(tài),那么就難以去管理其的生命周期,這里的flag就是標(biāo)記其此時(shí)的狀態(tài).這里的flag其實(shí)不止是對(duì)其生命周期有管理,也有存儲(chǔ)了里面的函數(shù)簽名,是否全局等信息

這個(gè)是flags具體指帶的信息,實(shí)際是個(gè)枚舉值

4.desc:

而desc你可以發(fā)現(xiàn),實(shí)際是個(gè)結(jié)構(gòu)體_block_desc,這個(gè)結(jié)構(gòu)體就存儲(chǔ)了block的描述信息:

desc結(jié)構(gòu)體的具體描述種類

值得一提的是它的copy和dispose函數(shù)指針,這兩個(gè)函數(shù)指針用來(lái)對(duì)block內(nèi)部變量進(jìn)行retain和relese操作,管理它們的生命周期.當(dāng)block從棧copy到堆中時(shí),其內(nèi)部的變量以及捕獲的外界變量將調(diào)用這個(gè)copy函數(shù)對(duì)其進(jìn)行retain操作進(jìn)行持有,而當(dāng)block跳出其所在的作用域不再被外界持有時(shí)候也即是當(dāng)其需要被釋放之時(shí),將調(diào)用dispose函數(shù)進(jìn)行將內(nèi)部的變量和捕獲變量進(jìn)行relese.

block如何進(jìn)行捕獲外界變量?

我們可以發(fā)現(xiàn)原代碼__block int a = 0;在.cpp文件中已經(jīng)被變成__block_byref_a_0 *a;

我們點(diǎn)進(jìn)去查看:

這里是a變量結(jié)構(gòu)體

這說(shuō)明當(dāng)我們用__block標(biāo)注外界變量時(shí)候,block將其轉(zhuǎn)為了一個(gè)結(jié)構(gòu)體,并用指針指向它進(jìn)行持有.

內(nèi)部同樣是4個(gè)參數(shù):

1.__isa:

看到isa,我們就知道其實(shí)現(xiàn)在這個(gè)a也是個(gè)對(duì)象,這里的isa就是用來(lái)包裝這個(gè)結(jié)構(gòu)體的.通過(guò)這個(gè)isa可以區(qū)分它的具體類型.

2.int a:

這里存儲(chǔ)的就是捕獲的外界a的值.

3.__forwarding:

可以發(fā)現(xiàn)這個(gè)__forwarding指針指向的結(jié)構(gòu)體類型與__block a的類型一致,實(shí)質(zhì)上這個(gè)forwarding也是指向的自身.這個(gè)__forwarding其實(shí)是block安全取值的指針.

我們來(lái)看下它如何取值:

原代碼:

printf("%d",a);

替換后:

這里是block執(zhí)行的取值代碼

可以發(fā)現(xiàn)它是通過(guò)a->__forwarding->a來(lái)進(jìn)行訪問(wèn)其捕獲的外界變量

中間為何要通過(guò)__forwarding指針來(lái)取值呢?

其實(shí)因?yàn)閍是個(gè)局部變量保存在棧區(qū),而block是結(jié)構(gòu)體在堆區(qū),試想如果a在方法執(zhí)行完畢后那么原值會(huì)被系統(tǒng)釋放,此時(shí)如果block被調(diào)用如果是直接a->a(即是訪問(wèn)上圖中結(jié)構(gòu)體里的int a,這個(gè)int a 就是保存的外界的棧中的a),那么a能取到值嗎?肯定不可以的.

__forwarding指針的作用就是標(biāo)記當(dāng)block被copy到堆中,在捕獲的外界變量被__block標(biāo)記后,__forwarding指針就會(huì)將其原本指向棧中變量的地址轉(zhuǎn)為指向堆中的a結(jié)構(gòu)體,此時(shí)里面的a就會(huì)轉(zhuǎn)為堆里的a.所以通過(guò)a->__forwarding->a去取值是能夠保證正常取到a的.

這也就是為什么__block標(biāo)記后就能修改原a,而不進(jìn)行這樣標(biāo)記就無(wú)法修改值,因?yàn)閏語(yǔ)言里的a是基本變量,基本變量的傳值是值傳遞.而被__block進(jìn)行捕獲后已經(jīng)轉(zhuǎn)為了對(duì)象,拷貝進(jìn)堆區(qū)后傳到block內(nèi)的其實(shí)是在堆區(qū)的a地址,這里的傳值是地址傳遞!(鑒于篇幅有限,就不再實(shí)驗(yàn)演示了,有興趣的可以自己打印下地址進(jìn)行驗(yàn)證)

4.__size:

這里的size顧名思義其實(shí)就是標(biāo)記當(dāng)前結(jié)構(gòu)體所占大小(字節(jié)數(shù)).

5.__flags:

這里的__flags是一個(gè)標(biāo)志位變量.

這里再留個(gè)懸念,大家思考下如果變量沒(méi)有被__block標(biāo)注,在block中又如何展現(xiàn)呢?

(其實(shí)答案應(yīng)該顯而易見吧?haha)

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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