這篇文章繼續(xù)上一篇Block深入淺出 (一)講解block的存儲和copy問題
三 Block的存儲和copy操作
block的三種類型
全局Block(_NSConcreteGlobalBlock)
棧Block (_NSConcreteStackBlock)
-
堆Block (_NSConcreteMallocBlock)
這三種block在內(nèi)存的存儲區(qū)域如下圖:
1.png 全局Block存在于data段,相當(dāng)于單例
棧Block存在于棧區(qū),超出作用域立即銷毀,由系統(tǒng)管理
堆Block存在于堆區(qū),是一個帶引用計(jì)數(shù)的對象,需要程序員自己管理其內(nèi)存
遇到一個Block,我們怎么知道這個Block的存儲位置呢(只討論ARC下)
- block不訪問外部變量(包括棧中和堆中的變量),此block即不在棧中也不在堆中,此時為全局Block
- block訪問外部變量默認(rèn)存儲在堆區(qū)(實(shí)際是存在于棧區(qū),但ARC情況下系統(tǒng)又自動拷貝到了堆區(qū)),自動釋放。
ARC下,訪問外部變量的block為什么要自動從棧區(qū)拷貝到了堆區(qū)呢?
棧上的block,如果其所屬的變量作用域結(jié)束,該block被廢棄,如同一般的自動變量,同時block中的__block變量也同時被廢棄。所以為了解決超過作用域就被釋放的問題,我們需要把block復(fù)制到堆區(qū),延長其生命周期。在ARC下,大多數(shù)情況下編譯器會恰當(dāng)?shù)倪M(jìn)行判斷是否需要將block從棧復(fù)制到堆,如果需要,編譯器會自動生成將block從棧復(fù)制到堆區(qū)的代碼。block的復(fù)制操作執(zhí)行的是copy實(shí)例方法,block只要調(diào)用了copy方法,棧block就會變成堆block。
例如下面的一個返回值為block類型的方法
- (UILabel*(^)(NSString*))TM_Text {
UILabel*(^block)(NSString*) = ^(NSString* name){
self.text = name;
return self;
};
return block;
}
- 上面的方法返回的block默認(rèn)是存在于棧上,所以方法執(zhí)行完畢之后,block的作用域就結(jié)束了,block會被廢棄,但在ARC下有效,這種情況編輯器會自動對block做一次copy處理。
- 由于將block從棧上復(fù)制到堆上相當(dāng)消耗CPU,所以當(dāng)block設(shè)置在棧上也夠使用時,就不要復(fù)制了,因?yàn)榇藭r的復(fù)制只是在消耗CPU資源。
不同類型的block執(zhí)行copy方法的效果圖如下:
| block | 存儲區(qū)域 | 執(zhí)行copy之后的效果 |
|---|---|---|
| 全局Block | 全局data段 | 什么都不做 |
| 棧Block | 棧區(qū) | 從棧區(qū)復(fù)制到堆區(qū) |
| 堆Block | 堆區(qū) | 引用計(jì)數(shù)增加 |
所以在ARC下,不管是何種block,用copy方法都不會引起任何問題,在不確定時調(diào)用copy方法即可。
__block變量與__forwarding
在上一篇文章我們知道使用__block修飾的int變量變成了__Block_byref_a_0的結(jié)構(gòu)體類型,想要訪問變量本身的值需要使用該結(jié)構(gòu)體中__forwarding指針指向的地址值。那在對block進(jìn)行copy操作之后,__block修飾的變量也被copy到了堆區(qū),那么訪問該變量是訪問棧上還是堆上的呢?

通過上圖可以知道原本棧上__block修飾的變量的__forwarding指針指向自己,而block進(jìn)行copy操作之后,__block修飾的變量也copy了一份到堆區(qū),并且堆區(qū)的此變量的__forwarding指向自己,而棧上的此變量的__forwarding指向了堆區(qū)的變量,這樣就實(shí)現(xiàn)了不管是該變量在堆區(qū)還是棧區(qū),也不管是在block內(nèi)部還是外部訪問該變量,都能順利的訪問同一個__block變量。
