ARC 下向 NSArray 添加 Block 元素的一個小坑

原文地址:http://matrixzk.github.io/blog/20150518/store_blocks_in_NSArray/

一直以來我都認為在 ARC 下焦读,給 Cocoa 框架的集合類侥袜,如 NSArray睁冬,添加 Block 類型的元素時挎春,Block 是會被編譯器自動執(zhí)行 copy 操作的。而且一直以來的實踐也驗證了這一事實豆拨。但今天在測試如下一段代碼時出現(xiàn)了問題直奋。

問題描述

先看下出問題的測試代碼:

id getBlockArray() {
    int val = 12;
    NSArray *arr = [[NSArray alloc] initWithObjects:^{NSLog(@"block1 val = %d", val);},
                                                    ^{NSLog(@"block2 val = %d", val);},
                                                    nil];
    return arr;
}

typedef void (^MyBlock)(void);

int main(int argc, const char * argv[]) {
    id blkArray = [obj getBlockArray];
    MyBlock block1 = blkArray[0];
    MyBlock block2 = blkArray[1]; // EXC_BAD_ACCESS, crash !!!

    blcok1();
    blcok2();
    return 0;
}

如上所示,在獲取數(shù)組中第二個 Block 元素時施禾,crash 了脚线,原因是 EXC_BAD_ACCESS,即訪問了已被釋放的無效內(nèi)存弥搞。很奇怪邮绿,調(diào)試打印 arr,輸出如下:

<__NSArrayM 0x100300500>(
    <__NSMallocBlock__: 0x100300410>,
    <__NSStackBlock__: 0x7fff5fbff750>
)

居然一個在堆上攀例,一個在棧上船逮,這。粤铭。挖胃。有些挑戰(zhàn)三觀了。

測試驗證

確實很奇怪梆惯,那不妨來試試給數(shù)組填充元素的其他方式吧:

- (id)getBlockArray {
    int val = 10;

    NSMutableArray *arr = [[NSMutableArray alloc] init];
    [arr addObject:^{NSLog(@"block1 val = %d", val);}];
    [arr addObject:^{NSLog(@"block2 val = %d", val);}];

    return arr;
}

以及:

int val = 10;
NSArray *arr = @[^{NSLog(@"block1 val = %d", val);}, ^{NSLog(@"block2 val = %d", val);}];

這兩種情況下調(diào)試打印 arr酱鸭,輸出如下:

<__NSArrayM 0x100100250>(
    <__NSMallocBlock__: 0x100200550>,
    <__NSMallocBlock__: 0x1003007e0>
)

可以看到都沒問題,作為 addObject: 參數(shù)添加進來的兩個 Block 元素垛吗,都被編譯器自動執(zhí)行了 copy 操作凹髓,這樣 Block 的類型就變成了 __NSMallocBlock,被拷貝到了堆上怯屉。好險扁誓,三觀稍微又正了點兒。但文章開頭的問題究竟是什么原因呢蚀之?

探尋原因

比較上邊的測試代碼和出問題的代碼蝗敢,同樣都是 ARC 的測試環(huán)境,為什么問題代碼中數(shù)組的兩個 Block 元素足删,第一個在堆上寿谴,第二個在棧上呢?聯(lián)想到像測試代碼中這樣失受,將 Block 拷貝到堆上的操作是編譯器在編譯時完成的讶泰,那問題會不會出在初始化方法上呢咏瑟?然后點開出問題的那個 API:

- (instancetype)initWithObjects:(id)firstObj, ... NS_REQUIRES_NIL_TERMINATION;

果然!這里的參數(shù)是可變個數(shù)的痪署,而且只有第一個參數(shù)顯式的聲明為 id 類型码泞。這下就能解釋問題代碼中,為什么第一個 Block 元素在堆上而第二個卻在棧上:因為只有第一個參數(shù)顯式的聲明為 id 類型狼犯,所以編譯器在編譯階段只能意識到需要對第一個作為參數(shù)傳進來的 Block 進行 copy 處理余寥。為了驗證這一猜測,下面顯式得把后邊的 Block 傳參強制轉(zhuǎn)換為 id 類型悯森,讓編譯器看到它:

NSArray *arr = [[NSArray alloc] initWithObjects:^{NSLog(@"block1 val = %d", val);},
                                                (id)^{NSLog(@"block2 val = %d", val);},
                                                nil];

代碼順利運行通過宋舷,沒有 crash,猜測得到了驗證瓢姻。這真算是一個坑點兒祝蝠。在 stackoverflow 上看到了一個對類似問題的討論,可以參考下:Storing Blocks in an Array幻碱。

擴展

另外需要注意的一點是绎狭,在 MRC 下,向方法或函數(shù)的參數(shù)中傳遞 Block 時褥傍,除了以下兩種情況坟岔,都需要手動 copy 一下:

  • Cocoa 框架中的方法名含有 usingBlock 的方法。例如 NSArray 的 enumerateObjectsUsingBlock 實例方法摔桦。
  • GCD 的 API社付。例如,dispatch_async 函數(shù)邻耕。

在 ARC 下鸥咖,除了上述兩種情況外,在如下兩種情況兄世,編譯器也幫我們自動做了 copy 操作:

  • Block 作為函數(shù)或方法的返回值返回時啼辣。(此場景和 ARC 下普通的對象作為函數(shù)或方法返回值返回時的場景一致)
  • 將 Block 賦值給附有 __strong 修飾符的變量時。(ARC 下的局部變量和成員變量默認都是 __strong的御滩,只是作用域不同)

這里有一個有趣的小測試Objective-C Blocks Quiz鸥拧,可以測下自己對 Block 的理解。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末削解,一起剝皮案震驚了整個濱河市富弦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌氛驮,老刑警劉巖腕柜,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡盏缤,警方通過查閱死者的電腦和手機砰蠢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唉铜,“玉大人台舱,你說我怎么就攤上這事√读鳎” “怎么了竞惋?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長幻枉。 經(jīng)常有香客問我,道長诡蜓,這世上最難降的妖魔是什么熬甫? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮蔓罚,結(jié)果婚禮上椿肩,老公的妹妹穿的比我還像新娘。我一直安慰自己豺谈,他們只是感情好郑象,可當我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著茬末,像睡著了一般厂榛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丽惭,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天击奶,我揣著相機與錄音,去河邊找鬼责掏。 笑死柜砾,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的换衬。 我是一名探鬼主播痰驱,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瞳浦!你這毒婦竟也來了担映?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤叫潦,失蹤者是張志新(化名)和其女友劉穎另萤,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡四敞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年泛源,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忿危。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡达箍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铺厨,到底是詐尸還是另有隱情缎玫,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布解滓,位于F島的核電站赃磨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏洼裤。R本人自食惡果不足惜邻辉,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腮鞍。 院中可真熱鬧值骇,春花似錦、人聲如沸移国。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迹缀。三九已至使碾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間祝懂,已是汗流浹背部逮。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嫂易,地道東北人兄朋。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像怜械,于是被迫代替她去往敵國和親颅和。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,665評論 2 354

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