《Objective-C高級編程》Blocks 閱讀筆記 item5(Block存儲域)

《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)存中的位置如下圖:

Snip20160217_16.png

注意

  • 由于_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就被廢棄皇拣。如圖:

Snip20160217_17.png

為此,Blocks提供了將Block和__block變量從棧上復(fù)制到堆上的方法來解決這個問題薄嫡。如圖:

Snip20160217_18.png

實現(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不會被廢棄。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末磅摹,一起剝皮案震驚了整個濱河市滋迈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌户誓,老刑警劉巖杀怠,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異厅克,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)橙依,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門证舟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硕旗,“玉大人,你說我怎么就攤上這事女责∑崦叮” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵抵知,是天一觀的道長墙基。 經(jīng)常有香客問我,道長刷喜,這世上最難降的妖魔是什么残制? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮掖疮,結(jié)果婚禮上初茶,老公的妹妹穿的比我還像新娘。我一直安慰自己浊闪,他們只是感情好恼布,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著搁宾,像睡著了一般折汞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盖腿,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天爽待,我揣著相機(jī)與錄音,去河邊找鬼奸忽。 笑死堕伪,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的栗菜。 我是一名探鬼主播欠雌,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼疙筹!你這毒婦竟也來了富俄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤而咆,失蹤者是張志新(化名)和其女友劉穎霍比,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暴备,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡悠瞬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浅妆。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡望迎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凌外,到底是詐尸還是另有隱情辩尊,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布康辑,位于F島的核電站摄欲,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏疮薇。R本人自食惡果不足惜胸墙,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惦辛。 院中可真熱鬧劳秋,春花似錦、人聲如沸胖齐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呀伙。三九已至补履,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剿另,已是汗流浹背箫锤。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留雨女,地道東北人谚攒。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像氛堕,于是被迫代替她去往敵國和親馏臭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容