《Objective-C高級編程》Blocks 閱讀筆記系列
《Objective-C高級編程》Blocks 閱讀筆記 item1(Blocks概要和模式)
《Objective-C高級編程》Blocks 閱讀筆記 item2(Block的實質(zhì))
《Objective-C高級編程》Blocks 閱讀筆記 item3(截獲自動變量值)
《Objective-C高級編程》Blocks 閱讀筆記 item4(__block說明符)
《Objective-C高級編程》Blocks 閱讀筆記 item5(Block存儲域)
《Objective-C高級編程》Blocks 閱讀筆記 item6(__block變量存儲域)
《Objective-C高級編程》Blocks 閱讀筆記 item7(截獲對象)
《Objective-C高級編程》Blocks 閱讀筆記 item8(__block變量和對象)
《Objective-C高級編程》Blocks 閱讀筆記 item9(Block循環(huán)引用)
《Objective-C高級編程》Blocks 閱讀筆記 item10(copy/release實例方法)
2.3 Blocks的實現(xiàn)
2.3.4 Block存儲域
從“__block說明符”一節(jié)中可知,*** Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類型的自動變量,___block變量轉(zhuǎn)換為__block的結(jié)構(gòu)體類型的自動變量国章。所謂結(jié)構(gòu)體類型的自動變量呻粹,即棧上生成的該結(jié)構(gòu)體的實例。 ***
表 Block與__block變量的實質(zhì)
名稱 | 實質(zhì) |
---|---|
Block | 棧上Block的結(jié)構(gòu)體實例 |
__block變量 | 棧上__block變量的結(jié)構(gòu)體實例 |
從“Block的實質(zhì)”一節(jié)中可知匆瓜,Block是Objective-C對象纷妆,并且該Block的類為_NSConcreteStackBlock扫外。此外,與之類似的還有兩個類:
- _NSConcreteStackBlock —— *** 該類對象設(shè)置在棧上 ***
- _NSConcreteGlobalBlock —— *** 該類對象設(shè)置在程序的數(shù)據(jù)區(qū)域(.data區(qū))中 ***
- _NSConcreteMallocBlock —— *** 該類對象設(shè)置在由malloc函數(shù)分配的內(nèi)存塊(即堆中) ***
在內(nèi)存中的位置如下圖:
注意
- 由于_NSConcreteGlobalBlock類生成的Block對象設(shè)置在程序的數(shù)據(jù)區(qū)域中(該類會運(yùn)用在“全局變量”的聲明中)仇冯,由此該類的結(jié)構(gòu)體實例的內(nèi)容不依賴于執(zhí)行時的狀態(tài),所以整個程序只需一個實例族操。
- 只在截獲自動變量時苛坚,Block的結(jié)構(gòu)體實例截獲的值才會根據(jù)執(zhí)行時的狀態(tài)變化,而在不截獲自動變量時色难,Block的結(jié)構(gòu)體實例每次截獲的值都相同泼舱。也就是說,即時在函數(shù)內(nèi)而不在記述廣域變量的地方使用Block語法時枷莉,只要Block不截獲自動變量娇昙,就可以將Block的結(jié)構(gòu)體實例設(shè)置在程序的數(shù)據(jù)區(qū)域。
總結(jié)
Block為_NSConcreteGlobalBlock類對象(即Block配置在程序的數(shù)據(jù)區(qū)域中)的情況有兩種:
- 記述全局變量的地方有Block語法時
- Block語法表達(dá)式中不使用“應(yīng)截獲的自動變量”時
除此之外的Block語法生成的Block為_NSConcreteStackBlock類對象(即類對象設(shè)置在棧上)笤妙。
而冒掌,_NSConcreteMallocBlock類是Block超出變量作用域可存在的原因。
遺留問題
“__block說明符”一節(jié)中遺留的問題:
- Block超出變量作用域可存在的原因
- __block變量的結(jié)構(gòu)體成員變量__forwarding存在的原因
配置在全局變量的Block危喉,從變量作用域外也可以通過指針安全的使用宋渔,而配置在棧上的Block,如果其所屬的變量作用域結(jié)束辜限,該Block就被廢棄皇拣。如圖:
為此,Blocks提供了將Block和__block變量從棧上復(fù)制到堆上的方法來解決這個問題薄嫡。如圖:
實現(xiàn)機(jī)制:
復(fù)制到堆上的Block將_NSConcreteMallocBlock類對象寫入Block的結(jié)構(gòu)體實例的成員變量isa氧急。
impl.isa = &_NSConcreteMallocBlock;
而__block變量的結(jié)構(gòu)體成員變量__forwarding可以實現(xiàn)無論__變量配置在棧上還是堆上時都能夠正確地訪問__block變量。
ARC有效時毫深,大多數(shù)情況下Block從棧上復(fù)制到堆上的代碼由編譯器實現(xiàn)
實際上吩坝,ARC有效時,大多數(shù)編譯器會恰當(dāng)?shù)剡M(jìn)行判斷哑蔫,自動生成將Block從棧上復(fù)制到堆上的代碼钉寝。
typedef int (^blk_t)(int);
blk_t func(int rate)
{
return ^(int count){return rate * count;};
}
該源代碼中的函數(shù)會返回配置在棧上的Block。即當(dāng)程序執(zhí)行從該函數(shù)返回函數(shù)調(diào)用方時闸迷,變量作用域結(jié)束嵌纲,因此棧上的Block也被廢棄。雖然有這樣的問題腥沽,但該源代碼通過對應(yīng)ARC的編譯器可轉(zhuǎn)換如下:
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);
}
理解:
- ARC有效時逮走,blk_t tmp 相當(dāng)于blk_t __strong tmp.
- objc_retainBlock實際上是Block_copy函數(shù)。
詳細(xì)的注釋:
blk_t func(int rate)
{
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
/*
* 將通過Block語法生成的Block(即配置在棧上的Block結(jié)構(gòu)體實例)
* 賦值給相當(dāng)于Block類型的變量tmp
*/
tmp = _Block_copy(tmp);
/*
* _Block_copy函數(shù)
* 將棧上的Block復(fù)制到堆上
* 復(fù)制后今阳,將堆上的地址作為指針賦值給變量tmp
*/
return objc_autoreleaseReturnValue(tmp);
/*
* 將堆上的Block作為Objective-C對象
* 注冊到autoreleasepool中师溅,然后返回該對象
*/
}
*** 將Block作為函數(shù)返回值返回時茅信,編譯器會自動生成復(fù)制到堆上的代碼。 ***
在少數(shù)情況下墓臭,Block從棧上復(fù)制到堆上的代碼的手動實現(xiàn)
如果*** 向方法或函數(shù)的參數(shù)中傳遞Block時 ***蘸鲸,編譯器將不能進(jìn)行判斷,需要使用“copy實例方法”手動復(fù)制起便。
但是棚贾,以下方法或函數(shù)不需要手動復(fù)制:
- Cocoa框架的方法且方法名中含有usingBlock等時
- Grand Central Dispathc 的API
*** 對Block語法調(diào)用copy方法 ***
- (id)getBlockArray
{
int val = 10;
return [[NSArray alloc] initWithObjects: ^{NSLog(@"blk:%d", val);},
^{NSLog(@"blk:%d", val);}, nil];
}
id obj = getBlockArray();
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();
該源代碼的blk(),即Block在執(zhí)行時發(fā)生異常榆综,應(yīng)用程序強(qiáng)制結(jié)束妙痹。這是由于*** getBlockArray函數(shù)執(zhí)行結(jié)束時,棧上的Block被廢棄的緣故鼻疮。 *** 此時怯伊,編譯器不能判斷是否需要復(fù)制。也可以不讓編譯器進(jìn)行判斷判沟,而使其在所有情況下都能復(fù)制耿芹。但將Block從棧上復(fù)制到堆上是相當(dāng)消耗CPU的。當(dāng)Block設(shè)置在棧上也能夠使用時挪哄,將Block從棧上復(fù)制到堆上只是在浪費(fèi)CPU資源吧秕。因此只在此情形下讓編程人員手動進(jìn)行復(fù)制。
對源代碼修改一下迹炼,便可正常運(yùn)行:
- (id)getBlockArray
{
int val = 10;
return [[NSArray alloc] initWithObjects: [^{NSLog(@"blk:%d", val);} copy],
[^{NSLog(@"blk:%d", val);} copy], nil];
}
id obj = getBlockArray();
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();
*** 對Block類型變量調(diào)用copy方法 ***
typedef int (^blk_t)(int);
blk_t blk = ^(int count){ return rate * count;};
blk = [blk copy];
按配置Block的存儲域砸彬,使用copy方法產(chǎn)生的復(fù)制效果
表 Block的副本
Block的類 | 副本源的配置存儲域 | 復(fù)制效果 |
---|---|---|
_NSConcreteStackBlock | 棧 | 從棧復(fù)制到堆 |
_NSConcreteGlobalBlock | 程序的數(shù)據(jù)區(qū)域 | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用計數(shù)增加 |
不管Block配置在何處,用copy方法復(fù)制都不會引起任何問題斯入。在不確定時調(diào)用copy方法即可砂碉。
在ARC有效時,多次調(diào)用copy方法完全沒有問題
blk = [[[[blk copy] copy] copy] copy];
// 經(jīng)過多次復(fù)制刻两,變量blk仍然持有Block的強(qiáng)引用增蹭,該Block不會被廢棄。