block本質(zhì):
每個(gè)OC對(duì)象內(nèi)部都有isa指針,block也不例外;block其實(shí)就是封裝函數(shù)調(diào)用,以及函數(shù)調(diào)用環(huán)境的OC對(duì)象。
block底層結(jié)構(gòu):

block的類(lèi)型:

常見(jiàn)的有以下三種block:
NSMallocBlock :存放在堆區(qū)的 Block
NSStackBlock??: 存放在棧區(qū)的 Block
NSGlobalBlock : 存放在全局區(qū)的 Block
當(dāng)block 沒(méi)有截獲外部變量、截獲全局變量的都是屬于全局區(qū)的 Block,即 GlobalBlock;其余的都是棧區(qū)的 Block,即 StackBlock;
對(duì)于全局區(qū)的 Block,是不存在作用域的問(wèn)題,但是棧區(qū) Block 不同,在作用域結(jié)束后就會(huì) pop 出棧,__block 變量也是在棧區(qū)的,同理作用域結(jié)束也會(huì) pop 出棧。
為了解決作用域的問(wèn)題,block 提供了 copy 函數(shù),將 block 從棧復(fù)制到堆上,在 MRC 環(huán)境下需要我們自己調(diào)用 block_copy??函數(shù),這里就是為什么 MRC 下,我們?yōu)槭裁葱枰?copy 來(lái)修飾 Block 的原因。
MRC情況下:

ARC情況下:
編譯器會(huì)根據(jù)情況自動(dòng)添加copy,將棧上的block拷貝到堆上,比如以下情況:
1、block作為函數(shù)返回值
2、block賦值給__strong指針
3、block作為Cocoa API中的方法名含有usingBlock的方法參數(shù)時(shí)
4、block作為GCD API參數(shù)時(shí)
每一種類(lèi)型的block調(diào)用copy后的結(jié)果如下:

block捕獲機(jī)制:


因?yàn)閍uto的局部變量在離開(kāi)作用域會(huì)銷(xiāo)毀,所以block內(nèi)部使用值傳遞的方式捕獲到,因此在block內(nèi)部不能更改auto變量的值,像int因?yàn)橹凳侵苯哟鎯?chǔ)在變量?jī)?nèi)存里面,所以不能更改;而static因?yàn)槭侵羔槀鬟f所有能更改。


因?yàn)镹SMutableArray 變量里面其實(shí)存的是指針地址,所以直接操作指針里面的內(nèi)容是可以的,如果直接更改變量存儲(chǔ)的指針地址就不行。
如果是全局變量不會(huì)捕獲,不管在哪個(gè)函數(shù)都能直接訪問(wèn);局部變量因?yàn)樽饔糜虻膯?wèn)題,可能存在跨函數(shù)訪問(wèn)所以會(huì)捕獲。
如果block中引用實(shí)例變量(比如說(shuō)name)不管是通過(guò)self.name還是_name,block都會(huì)捕獲self對(duì)象.因?yàn)榉椒ǖ恼{(diào)用默認(rèn)傳遞兩個(gè)參數(shù)(self,sel)所以self為局部變量,block引用局部變量都會(huì)捕獲。
既然block對(duì)局部auto變量是值傳遞,那么如果想要在block內(nèi)部修改auto變量怎么修改?
如果block外部變量是auto變量可以使用__block;如果是static靜態(tài)變量 或者全局變量,則不需要修飾也能更改。(參考捕獲機(jī)制)
原理:在ARC環(huán)境下,編譯器會(huì)盡可能給我們自動(dòng)添加 copy 操作。使用copy從棧復(fù)制到堆上,__block 修飾的變量也會(huì)從棧復(fù)制到堆上;為了結(jié)構(gòu)體 __block 變量無(wú)論在棧上還是在堆上,都可以正確的訪問(wèn)變量,我們需要 forwarding 指針;forwarding指針的作用:保證當(dāng)我們將 Block 從??截惖蕉阎?,修改的變量都是同一份。
forwarding原理:在 Block 從棧復(fù)制到堆上的時(shí)候,原本棧上結(jié)構(gòu)體的 forwarding 指針,會(huì)改變指向,直接指向堆上的結(jié)構(gòu)體。這樣子就可以保證之后我們都是訪問(wèn)同一個(gè)結(jié)構(gòu)體中的變量,這里就是為什么 __block 修飾的變量,在 Block 內(nèi)部中可以修改的原因了。

__block底層實(shí)現(xiàn):
當(dāng)__block變量在棧上時(shí):
并不會(huì)對(duì)指向的對(duì)象產(chǎn)生強(qiáng)引用。
當(dāng)__block變量被copy到堆上時(shí):
copy函數(shù)內(nèi)會(huì)調(diào)用_Block_object_assign函數(shù);_Block_object_assign函數(shù)會(huì)根據(jù)auto變量(對(duì)象類(lèi)型)的修飾符(__strong,__weak, __unsafe_unretained)做出相應(yīng)的操作形成強(qiáng)、弱引用;
當(dāng)__block變量被從堆上移除時(shí):
會(huì)調(diào)用Block內(nèi)部的dispose函數(shù) dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)?_Block_object_dispose函數(shù)會(huì)自動(dòng)釋放引用的auto變量,類(lèi)似于release。
block引起循環(huán)引用場(chǎng)景:
當(dāng) A 持有 B,B 又持有 A,這個(gè)時(shí)候就會(huì)出現(xiàn)循環(huán)引用。Block 對(duì)于外部變量都會(huì)追加到結(jié)構(gòu)體中,所以在實(shí)現(xiàn) Block 時(shí)候需要注意這個(gè)問(wèn)題。
解決方案:
ARC 環(huán)境一般我們用 __weak 來(lái)打破,MRC 環(huán)境的話,我們可以使用 __block 來(lái)打破循環(huán)引用。