Block存儲(chǔ)域
</br>
1咒林、全局塊出現(xiàn)的2種場(chǎng)景
impl.isa = &_NSConcreteGlobalBlock;
(1) 記述全局變量的地方有Block語(yǔ)法時(shí)
void (^blk)(void) = ^{ printf("Global Block\n"); };
int main() {
...
}
(2)Block語(yǔ)法的表達(dá)式中不使用截獲的自動(dòng)變量時(shí)
for (int rate = 0; rate < 10; ++rate) {
(void) (^blk)(void) = ^{int count){ return count; };
}
2摆尝、棧塊
impl.isa = &_NSConcreteStackBlock;
除了上述全局塊的場(chǎng)景外搓逾,Block語(yǔ)法生成的Block對(duì)象是_NSConcreteStackBlock,因此設(shè)置在棧上趁冈。
配置在全局變量上的Block皂岔,從變量作用域外也可以通過(guò)指針安全地使用。但是設(shè)置在棧上的Block单芜,如果其所屬的變量作用域結(jié)束蜕该,該Block就被廢棄,如同一般的自動(dòng)變量洲鸠。當(dāng)然堂淡,Block中的__block變量也同時(shí)被廢棄。
3扒腕、堆塊
impl.isa = &_NSConcreteMallocBlock;
為了解決棧塊在其變量作用域結(jié)束之后被廢棄(釋放)的問(wèn)題绢淀,我們需要把Block復(fù)制到堆中,延長(zhǎng)其生命周期瘾腰。(說(shuō)起延長(zhǎng)生命周期皆的,讓我想起在一個(gè)非alloc/new/copy/mutableCopy方法中創(chuàng)建對(duì)象后,把它添加到autoreleasepool的做法蹋盆。)
開(kāi)啟ARC時(shí)费薄,大多數(shù)情況下編譯器會(huì)恰當(dāng)?shù)剡M(jìn)行判斷是否有需要將Block從棧復(fù)制到堆,如果有栖雾,自動(dòng)生成將Block從棧上復(fù)制到堆上的代碼楞抡。Block的復(fù)制操作執(zhí)行的是copy實(shí)例方法。
看一個(gè)返回值為Block類(lèi)型的函數(shù)
typedef int (^blk_t)(int);
blk_t func(int rate) {
return ^(int count) { return rate * count; };
}
分析可知:上面的函數(shù)返回的Block是配置在棧上的析藕,所以返回函數(shù)調(diào)用方時(shí)召廷,Block變量作用域就結(jié)束了,Block會(huì)被廢棄。但在ARC有效柱恤,這種情況編譯器會(huì)自動(dòng)完成復(fù)制数初。
?轉(zhuǎn)換為C++代碼看其是如何做到的:
blk_t func(int rate) {
blk_t tmp = &__func_block_impl_0 (__func_block_func_0, &__func_block_desc_0_DATA, rate);
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValue(tmp);
}
第一條語(yǔ)句找爱,將通過(guò)Block語(yǔ)法生成的配置在棧上的Block梗顺,賦值給變量tmp。
第二條語(yǔ)句车摄,通過(guò)運(yùn)行時(shí)庫(kù)可知寺谤,objc_retainBlock函數(shù)實(shí)際上是_Block_copy函數(shù)。_Block_copy函數(shù)負(fù)責(zé)將棧上的Block賦值到堆上吮播,復(fù)制后將堆上的地址作為指針賦值給變量tmp变屁。
第三條語(yǔ)句,將堆上的Block作為Objective-C對(duì)象意狠,注冊(cè)到autoreleasepool中粟关,然后返回該對(duì)象。
編譯器不能進(jìn)行判斷的情況环戈,需要編程人員調(diào)用copy方法手動(dòng)復(fù)制:
- 向方法或函數(shù)的參數(shù)中傳遞Block時(shí)
如果在方法或函數(shù)中適當(dāng)?shù)貜?fù)制了傳遞過(guò)來(lái)的參數(shù)闷板,就不必在調(diào)用該方法或函數(shù)前手動(dòng)復(fù)制。以下方法或函數(shù)不用手動(dòng)復(fù)制:
- Cocoa框架的方法且方法名中含有usingBlock院塞,如NSArray類(lèi)的enumerateObjectsUsingBlock實(shí)例方法遮晚。
- Grand Central Dispatch的API,如dispatch_async函數(shù)拦止。
县遣!注意:將Block從棧上復(fù)制到堆上相當(dāng)消耗CPU,所以當(dāng)Block設(shè)置在棧上也能夠使用時(shí)汹族,就不要復(fù)制了萧求,因?yàn)榇藭r(shí)的復(fù)制只是在浪費(fèi)CPU資源。
Block的復(fù)制操作執(zhí)行的是copy實(shí)例方法顶瞒。不同類(lèi)型的Block使用copy方法的效果如下表
根據(jù)表得知夸政,Block在堆中copy會(huì)造成引用計(jì)數(shù)增加,這與其他Objective-C對(duì)象是一樣的搁拙。雖然Block在棧中也是以對(duì)象的身份存在秒梳,但是棧塊沒(méi)有引用計(jì)數(shù),因?yàn)椴恍枰伲覀兌贾罈^(qū)的內(nèi)存由編譯器自動(dòng)分配釋放酪碘。
至此,總結(jié)棧上的Block什么時(shí)候會(huì)復(fù)制到堆上:
- 調(diào)用Block的copy實(shí)例方法時(shí)
- Block作為函數(shù)返回值返回時(shí)
- 將Block賦值給附有__strong修飾符id類(lèi)型的類(lèi)或Block類(lèi)型變量時(shí)
- 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中傳遞Block時(shí)
其中盐茎,第一種情況需要程序猿手動(dòng)調(diào)用兴垦,后面三種情況由編譯器自動(dòng)完成(開(kāi)啟ARC)。