block 強弱引用(轉(zhuǎn))

Block的引用循環(huán)問題 (ARC & non-ARC)

2014-03-25 14:56 24799人閱讀 評論(2) 收藏 舉報

分類: iPhone(315)

目錄(?)[+]

2010年WWDC發(fā)布iOS4時Apple對Objective-C進行了一次重要的升級:支持Block。說到底這東西就是閉包绊含,其他高級語音例如Java和C++已有支持宝冕,第一次使用Block感覺滿簡單好用的允瞧,但是慢慢也遇到很多坑境蜕。本文聊聊ARC和non-ARC下Block使用中的引用循環(huán)問題,最近遇到了好幾次這種問題掉房,還是深入記錄下茫陆。先來套題目熱熱身,貌似能夠全部答對的人蠻少的

Block實現(xiàn)原理

首先探究下Block的實現(xiàn)原理逃延,由于Objective-C是C語言的超集览妖,既然OC中的NSObject對象其實是由C語言的struct+isa指針實現(xiàn)的,那么Block的內(nèi)部實現(xiàn)估計也一樣揽祥,以下三篇Blog對Block的實現(xiàn)機制做了詳細研究:

A look inside blocks: Episode 1

A look inside blocks: Episode 2

A look inside blocks: Episode 3

雖然實現(xiàn)細節(jié)看著頭痛讽膏,不過發(fā)現(xiàn)Block果然是和OC中的NSObject類似,也是用struct實現(xiàn)出來的東西拄丰。這個是LLVM項目compiler-rt分析的block頭文Block_private.h頭文件中關(guān)于Block的struct聲明:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

struct Block_descriptor {

unsigned long int reserved;

unsigned long int size;

void (*copy)(void *dst, void *src);

void (*dispose)(void *);

};

struct Block_layout {

void *isa;

int flags;

int reserved;

void (*invoke)(void *, ...);

struct Block_descriptor *descriptor;

/* Imported variables. */

};

我們發(fā)現(xiàn)Block_layout中也有一個isa指針府树,像極了NSobject內(nèi)部實現(xiàn)struct中的isa指針。這里的isa可能指向三種類型之一的Block:

_NSConcreteGlobalBlock:全局類型Block料按,在編譯器就已經(jīng)確定奄侠,直接放在代碼段__TEXT上。直接在NSLog中打印的類型為__NSGlobalBlock__载矿。

_NSConcreteStackBlock:位于棧上分配的Block垄潮,即__NSStackBlock__。

_NSConcreteMallocBlock:位于堆上分配的Block闷盔,即__NSMallocBlock__魂挂。

為什么會有這么多種類呢?首先來看全局類型Block馁筐,看例子:

1

2

3

4

5

6

7

8

9

10

11

12

void addBlock(NSMutableArray *array) {

[array addObject:^{

printf("global block\n");

}];

}

void example() {

NSMutableArray *array = [NSMutableArray array];

addBlock(array);

void (^block)() = [array objectAtIndex:0];

block();

}

為什么addBlock中添加到array中的Block屬于全局Block呢涂召?因為它不需要運行時(Runtime)任何的狀態(tài)來改變行為,不需要放在堆上或者棧上敏沉,直接編譯后在代碼段中即可果正,就像個c函數(shù)一樣炎码。這種類型的Block在ARC和non-ARC情況下沒有差別。

這個Block訪問了作用域外的變量d秋泳,在實現(xiàn)上就是這個block會多一個成員變量對應(yīng)這個d潦闲,在賦值block時會將方法exmpale中的d變量值復(fù)制到成員變量中,從而實現(xiàn)訪問迫皱。

1

2

3

4

5

6

7

void example() {

int d = 5;

void (^block)() = ^() {

printf("%d\n", d);

};

block();

}

如果要修改d呢歉闰?:

1

2

3

4

5

6

7

8

9

void example() {

int d = 5;

void (^block)() = ^() {

d++;

printf("%d\n", d);

};

block();

printf("%d\n", d);

}

由于局部變量d和這個block的實現(xiàn)不在同一作用域,僅僅在調(diào)用過程中用到了值傳遞卓起,所以不能直接修改和敬,而需要加一個標(biāo)識符__block int d = 5;,那么block就可以實現(xiàn)對這個局部變量的修改了戏阅。如果是這種block標(biāo)識的變量昼弟,在Block實現(xiàn)中不再是簡單的一個成員變量,而是對應(yīng)一個新的結(jié)構(gòu)體表示這個block變量奕筐。block的本質(zhì)是引入了一個新的Block_byref{$var_name}{$index}結(jié)構(gòu)體舱痘,被block關(guān)鍵字修飾的變量就被放到這個結(jié)構(gòu)體中。另外离赫,block結(jié)構(gòu)體通過引入Block_byref{$var_name}{$index}指針類型的成員芭逝,得以間接訪問到Block的外部變量。這樣對Block外的變量訪問從值傳遞轉(zhuǎn)變?yōu)橐迷ㄐ兀瑥亩辛诵薷膬?nèi)容的能力旬盯。

正常我們使用Block是在棧上生成的,離開了棧作用域便釋放了蹬刷,如果copy一個Block,那么會將這個Block copy到堆上分配频丘,這樣就不再受棧的限制办成,可以隨意使用啦。例如:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

typedef void (^TestBlock)();

TestBlock getBlock() {

char e = 'E';

void (^returnedBlock)() = ^{

printf("%c\n", e);

};

return returnedBlock;

}

void example() {

TestBlock block = getBlock();

block();

}

函數(shù)getBlock中聲明并賦值的returnedBlock搂漠,一開始是在棧上分配的迂卢,屬于NSStackBlock,如果是non-ARC情況下return這個NSStackBlock桐汤,那么其實已經(jīng)被銷毀了而克,在函數(shù)中example()使用時就會crash。如果是ARC情況下怔毛,getBlock返回的block會自動copy到堆上员萍,那么block的類型就是NSMallocBlock,可以在example()中繼續(xù)使用拣度。要在Non-ARC情況下正常運行碎绎,那么就應(yīng)該修改為:

1

2

3

4

5

6

7

TestBlock getBlock() {

char e = 'E';

void (^returnedBlock)() = ^{

printf("%c\n", e);

};

return [[returnedBlock copy] autorelease];

}

Block中的循環(huán)引用問題

扯了這么多螃壤,回到Block的循環(huán)引用問題,由于我們很多行為會導(dǎo)致Block的copy筋帖,而當(dāng)Block被copy時奸晴,會對block中用到的對象產(chǎn)生強引用(ARC下)或者引用計數(shù)加一(non-ARC下)。

如果遇到這種情況:

1

2

3

4

5

6

7

8

9

@property(nonatomic, readwrite, copy) completionBlock completionBlock;

//========================================

self.completionBlock = ^ {

if (self.success) {

self.success(self.responseData);

}

}

};

對象有一個Block屬性日麸,然而這個Block屬性中又引用了對象的其他成員變量寄啼,那么就會對這個變量本身產(chǎn)生強應(yīng)用,那么變量本身和他自己的Block屬性就形成了循環(huán)引用代箭。在ARC下需要修改成這樣:

1

2

3

4

5

6

7

8

9

@property(nonatomic, readwrite, copy) completionBlock completionBlock;

//========================================

__weak typeof(self) weakSelf = self;

self.completionBlock = ^ {

if (weakSelf.success) {

weakSelf.success(weakSelf.responseData);

}

};

也就是生成一個對自身對象的弱引用墩划,如果是倒霉催的項目還需要支持iOS4.3,就用__unsafe_unretained替代__weak梢卸。如果是non-ARC環(huán)境下就將__weak替換為__block即可走诞。non-ARC情況下,__block變量的含義是在Block中引入一個新的結(jié)構(gòu)體成員變量指向這個__block變量蛤高,那么__block typeof(self) weakSelf = self;就表示Block別再對self對象retain啦蚣旱,這就打破了循環(huán)引用邪媳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末走趋,一起剝皮案震驚了整個濱河市县袱,隨后出現(xiàn)的幾起案子模暗,更是在濱河造成了極大的恐慌走搁,老刑警劉巖府寒,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唱凯,死亡現(xiàn)場離奇詭異尔许,居然都是意外死亡喜庞,警方通過查閱死者的電腦和手機诀浪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來延都,“玉大人雷猪,你說我怎么就攤上這事∥浚” “怎么了求摇?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長殊者。 經(jīng)常有香客問我与境,道長,這世上最難降的妖魔是什么猖吴? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任摔刁,我火速辦了婚禮,結(jié)果婚禮上海蔽,老公的妹妹穿的比我還像新娘簸搞。我一直安慰自己扁位,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布趁俊。 她就那樣靜靜地躺著域仇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寺擂。 梳的紋絲不亂的頭發(fā)上暇务,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音怔软,去河邊找鬼垦细。 笑死,一個胖子當(dāng)著我的面吹牛挡逼,可吹牛的內(nèi)容都是我干的括改。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼家坎,長吁一口氣:“原來是場噩夢啊……” “哼嘱能!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起虱疏,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤惹骂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后做瞪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體对粪,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年装蓬,在試婚紗的時候發(fā)現(xiàn)自己被綠了著拭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡牍帚,死狀恐怖儡遮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情履羞,我是刑警寧澤峦萎,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布屡久,位于F島的核電站忆首,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏被环。R本人自食惡果不足惜糙及,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望筛欢。 院中可真熱鬧浸锨,春花似錦唇聘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至聪蘸,卻和暖如春宪肖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背健爬。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工控乾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娜遵。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓蜕衡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親设拟。 傳聞我的和親對象是個殘疾皇子慨仿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

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