Block中self的循環(huán)引用--摘自《禪與 Objective-C 編寫(xiě)的藝術(shù)》的中文翻譯

###? self的循環(huán)引用

當(dāng)使用代碼塊和異步分發(fā)的時(shí)候宇立,要注意避免引用循環(huán)∽耘猓總是使用`weak`來(lái)引用對(duì)象妈嘹,避免引用循環(huán)。(譯者注:這里更為優(yōu)雅的方式是采用影子變量@weakify/@strongify[這里有更為詳細(xì)的說(shuō)明](https://github.com/jspahrsummers/libextobjc/blob/master/extobjc/EXTScope.h))此外绍妨,把持有block的屬性設(shè)置為nil (比如`self.completionBlock = nil`)是一個(gè)好的實(shí)踐润脸。它會(huì)打破block捕獲的作用域帶來(lái)的引用循環(huán)柬脸。

**例子:**

```objective-c

__weak __typeof(self) weakSelf =self;

[selfexecuteBlock:^(NSData *data, NSError *error) {

? ? [weakSelf doSomethingWithData:data];

}];

```

**不要這樣:**

```objective-c

[selfexecuteBlock:^(NSData *data, NSError *error) {

? ? [selfdoSomethingWithData:data];

}];

```

**多個(gè)語(yǔ)句的例子:**

```objective-c

__weak __typeof(self)weakSelf =self;

[selfexecuteBlock:^(NSData *data, NSError *error) {

? ? __strong __typeof(weakSelf) strongSelf = weakSelf;

? ? if(strongSelf) {

? ? ? ? [strongSelf doSomethingWithData:data];

? ? ? ? [strongSelf doSomethingWithData:data];

? ? }

}];

```

**不要這樣:**

```objective-c

__weak __typeof(self)weakSelf =self;

[selfexecuteBlock:^(NSData *data, NSError *error) {

? ? [weakSelf doSomethingWithData:data];

? ? [weakSelf doSomethingWithData:data];

}];

```

你應(yīng)該把這兩行代碼作為 snippet 加到 Xcode 里面并且總是這樣使用它們。

```objective-c

__weak __typeof(self)weakSelf =self;

__strong __typeof(weakSelf)strongSelf = weakSelf;

```

這里我們來(lái)討論下block里面的self的`__weak`和`__strong`? 限定詞的一些微妙的地方毙驯。簡(jiǎn)而言之倒堕,我們可以參考self在block里面的三種不同情況。

1.直接在block里面使用關(guān)鍵詞self

2.在block外定義一個(gè)`__weak`的引用到self爆价,并且在block里面使用這個(gè)弱引用

3.在block外定義一個(gè)`__weak`的引用到self垦巴,并在在block內(nèi)部通過(guò)這個(gè)弱引用定義一個(gè)`__strong`? 的引用。

**方案 1. 直接在 block 里面使用關(guān)鍵詞 `self`**

如果我們直接在 block 里面用 self 關(guān)鍵字铭段,對(duì)象會(huì)在 block 的定義時(shí)候被 retain魂那,(實(shí)際上 block 是[copied][blocks_caveat13]? 但是為了簡(jiǎn)單我們可以忽略這個(gè))。一個(gè) const 的對(duì) self 的引用在 block 里面有自己的位置并且它會(huì)影響對(duì)象的引用計(jì)數(shù)稠项。如果這個(gè)block被其他的類使用并且(或者)彼此間傳來(lái)傳去涯雅,我們可能想要在 block 中保留 self,就像其他在 block 中使用的對(duì)象一樣. 因?yàn)樗麄兪莃lock執(zhí)行所需要的.

```objective-c

dispatch_block_t completionBlock = ^{

? ? NSLog(@"%@",self);

}

MyViewController *myController = [[MyViewController alloc] init...];

[selfpresentViewController:myController

?? ? ? ? ? ? ? ? ? animated:YES

?? ? ? ? ? ? ? ? completion:completionHandler];

```

沒(méi)啥大不了展运。但是如果通過(guò)一個(gè)屬性中的 `self` 保留 了這個(gè) block(就像下面的例程一樣),對(duì)象( self )保留了 block 會(huì)怎么樣呢活逆?

```objective-c

self.completionHandler = ^{

? ? NSLog(@"%@",self);

}

MyViewController *myController = [[MyViewController alloc] init...];

[selfpresentViewController:myController

?? ? ? ? ? ? ? ? ? animated:YES

?? ? ? ? ? ? ? ? completion:self.completionHandler];

```

這就是有名的 retain cycle, 并且我們通常應(yīng)該避免它。這種情況下我們收到 CLANG 的警告:

```objective-c

Capturing 'self' strongly inthisblock is likely to lead to a retain cycle(在block里面發(fā)現(xiàn)了`self`的強(qiáng)引用拗胜,可能會(huì)導(dǎo)致循環(huán)引用)

```

所以`__weak`就有用武之地了蔗候。

**方案 2. block 外定義一個(gè) `__weak` 引用到 self,并且在 block 里面使用這個(gè)弱引用**

這樣會(huì)避免循壞引用埂软,也是通常情況下我們的block作為類的屬性被self retain 的時(shí)候會(huì)做的锈遥。

```objective-c

__weak typeof(self) weakSelf =self;

self.completionHandler = ^{

? ? NSLog(@"%@", weakSelf);

};

MyViewController *myController = [[MyViewController alloc] init...];

[selfpresentViewController:myController

?? ? ? ? ? ? ? ? ? animated:YES

?? ? ? ? ? ? ? ? completion:self.completionHandler];

```

這個(gè)情況下 block 沒(méi)有 retain 對(duì)象并且對(duì)象在屬性里面 retain 了 block 。所以這樣我們能保證了安全的訪問(wèn) self勘畔。 不過(guò)糟糕的是所灸,它可能被設(shè)置成 nil 的。問(wèn)題是:如何讓 self 在 block 里面安全地被銷毀炫七。

考慮這么個(gè)情況:block 作為屬性(property)賦值的結(jié)果爬立,從一個(gè)對(duì)象被復(fù)制到另一個(gè)對(duì)象(如 myController),在這個(gè)復(fù)制的 block 執(zhí)行之前万哪,前者(即之前的那個(gè)對(duì)象)已經(jīng)被解除分配侠驯。

下面的更有意思。

**方案 3. block 外定義一個(gè) `__weak` 引用到 self奕巍,并在在 block 內(nèi)部通過(guò)這個(gè)弱引用定義一個(gè)`__strong`? 的引用**

你可能會(huì)想吟策,首先,這是避免retain cycle? 警告的一個(gè)技巧的止。

這不是重點(diǎn)檩坚,這個(gè)self的強(qiáng)引用是在block執(zhí)行時(shí)被創(chuàng)建的,但是否使用self在block定義時(shí)就已經(jīng)定下來(lái)了,因此self (在block執(zhí)行時(shí))會(huì)被retain.

[Apple文檔][blocks_caveat1]中表示"為了non-trivial cycles效床,你應(yīng)該這樣":

```objective-c

MyViewController *myController = [[MyViewController alloc] init...];

// ...

MyViewController* __weak weakMyController = myController;

myController.completionHandler= ^(NSInteger result) {

? ? MyViewController *strongMyController = weakMyController;

? ? if(strongMyController) {

? ? ? ? // ...

? ? ? ? [strongMyController dismissViewControllerAnimated:YES completion:nil];

? ? ? ? // ...

? ? }

? ? else{

? ? ? ? // Probably nothing...

? ? }

};

```

首先,我覺(jué)得這個(gè)例子看起來(lái)是錯(cuò)誤的权谁。如果block本身在completionHandler屬性中被retain了剩檀,那么self如何被delloc和在block之外賦值為nil呢? completionHandler屬性可以被聲明為? `assign`或者`unsafe_unretained`的,來(lái)允許對(duì)象在block被傳遞之后被銷毀旺芽。

我不能理解這樣做的理由沪猴,如果其他對(duì)象需要這個(gè)對(duì)象(self),block 被傳遞的時(shí)候應(yīng)該 retain 對(duì)象采章,所以 block 應(yīng)該不被作為屬性存儲(chǔ)运嗜。這種情況下不應(yīng)該用 `__weak`/`__strong`

總之,其他情況下悯舟,希望 weakSelf 變成 nil 的話担租,就像第二種情況解釋那么寫(xiě)(在 block 之外定義一個(gè)弱應(yīng)用并且在 block 里面使用)。

還有抵怎,Apple的 "trivial block" 是什么呢奋救。我們的理解是 trivial block 是一個(gè)不被傳送的 block ,它在一個(gè)良好定義和控制的作用域里面反惕,weak 修飾只是為了避免循環(huán)引用尝艘。

雖然有 Kazuki Sakamoto 和 Tomohiko Furumoto) 討論的[一][blocks_caveat2][些][blocks_caveat3][的][blocks_caveat4][在線][blocks_caveat5][參考][blocks_caveat6],? [Matt Galloway][blocks_caveat16]的([Effective Objective-C 2.0][blocks_caveat14][Pro Multithreading and Memory Management for iOS and OS X][blocks_caveat15],大多數(shù)開(kāi)發(fā)者始終沒(méi)有弄清楚概念姿染。

在 block 內(nèi)用強(qiáng)引用的優(yōu)點(diǎn)是背亥,搶占執(zhí)行的時(shí)候的魯棒性。在 block 執(zhí)行的時(shí)候, 再次溫故下上面的三個(gè)例子:

**方案 1. 直接在 block 里面使用關(guān)鍵詞 `self`**

如果block被屬性retain悬赏,self和block之間會(huì)有一個(gè)循環(huán)引用并且它們不會(huì)再被釋放狡汉。如果block被傳送并且被其他的對(duì)象copy了,self在每一個(gè)copy里面被retain

**方案 2. block 外定義一個(gè) `__weak` 引用到 self闽颇,并且在 block 里面使用這個(gè)弱引用**

不管 block 是否通過(guò)屬性被 retain 轴猎,這里都不會(huì)發(fā)生循環(huán)引用。如果 block 被傳遞或者 copy 了进萄,在執(zhí)行的時(shí)候捻脖,weakSelf 可能已經(jīng)變成 nil。

block 的執(zhí)行可以搶占中鼠,而且對(duì) weakSelf 指針的調(diào)用時(shí)序不同可以導(dǎo)致不同的結(jié)果(如:在一個(gè)特定的時(shí)序下 weakSelf 可能會(huì)變成nil)可婶。

```objective-c

__weak typeof(self) weakSelf =self;

dispatch_block_t block = ^{

? ? [weakSelf doSomething];// weakSelf != nil

? ? // preemption, weakSelf turned nil

? ? [weakSelf doSomethingElse];// weakSelf == nil

};

```

**方案 3. block 外定義一個(gè) `__weak` 引用到 self,并在在 block 內(nèi)部通過(guò)這個(gè)弱引用定義一個(gè)`__strong`? 的引用援雇。**

不管 block 是否通過(guò)屬性被 retain 矛渴,這里也不會(huì)發(fā)生循環(huán)引用。如果 block 被傳遞到其他對(duì)象并且被復(fù)制了,執(zhí)行的時(shí)候具温,weakSelf 可能被nil蚕涤,因?yàn)閺?qiáng)引用被賦值并且不會(huì)變成nil的時(shí)候,我們確保對(duì)象 在 block 調(diào)用的完整周期里面被 retain了铣猩,如果搶占發(fā)生了揖铜,隨后的對(duì) strongSelf 的執(zhí)行會(huì)繼續(xù)并且會(huì)產(chǎn)生一樣的值。如果 strongSelf 的執(zhí)行到 nil达皿,那么在 block 不能正確執(zhí)行前已經(jīng)返回了天吓。

```objective-c

__weak typeof(self) weakSelf =self;

myObj.myBlock= ^{

? ? __strong typeof(self) strongSelf = weakSelf;

? ? if(strongSelf) {

? ? ? [strongSelf doSomething];// strongSelf != nil

? ? ? // preemption, strongSelf still not nil(搶占的時(shí)候,strongSelf還是非nil的)

? ? ? [strongSelf doSomethingElse];// strongSelf != nil

? ? }

? ? else{

? ? ? ? // Probably nothing...

? ? ? ? return;

? ? }

};

```

在ARC條件中峦椰,如果嘗試用 `->` 符號(hào)訪問(wèn)一個(gè)實(shí)例變量龄寞,編譯器會(huì)給出非常清晰的錯(cuò)誤信息:

```objective-c

Dereferencing a __weak pointer is not allowed due to possiblenullvalue caused by race condition, assign it to a strong variable first. (對(duì)一個(gè)__weak指針的解引用不允許的,因?yàn)榭赡茉诟?jìng)態(tài)條件里面變成null,所以先把他定義成strong的屬性)

```

可以用下面的代碼展示

```objective-c

__weak typeof(self) weakSelf =self;

myObj.myBlock= ^{

? ? id localVal = weakSelf->someIVar;

};

```

在最后

***方案 1**:只能在block不是作為一個(gè)property的時(shí)候使用汤功,否則會(huì)導(dǎo)致retain cycle物邑。

***方案 2**:? 當(dāng)block被聲明為一個(gè)property的時(shí)候使用。

* **方案 3**: 和并發(fā)執(zhí)行有關(guān)滔金。當(dāng)涉及異步的服務(wù)的時(shí)候拂封,block 可以在之后被執(zhí)行,并且不會(huì)發(fā)生關(guān)于 self 是否存在的問(wèn)題鹦蠕。

[blocks_caveat1]: https://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html

[blocks_caveat2]: https://dhoerl.wordpress.com/2013/04/23/i-finally-figured-out-weakself-and-strongself/

[blocks_caveat3]: http://blog.random-ideas.net/?p=160

[blocks_caveat4]: http://stackoverflow.com/questions/7904568/disappearing-reference-to-self-in-a-block-under-arc

[blocks_caveat5]: http://stackoverflow.com/questions/12218767/objective-c-blocks-and-memory-management

[blocks_caveat6]: https://github.com/AFNetworking/AFNetworking/issues/807

[blocks_caveat10]: https://twitter.com/pedrogomes

[blocks_caveat11]: https://twitter.com/dmakarenko

[blocks_caveat12]: https://ef.com

[blocks_caveat13]: https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW4

[blocks_caveat14]: http://www.effectiveobjectivec.com/

[blocks_caveat15]: http://www.amazon.it/Pro-Multithreading-Memory-Management-Ios/dp/1430241160

[blocks_caveat16]: https://twitter.com/mattjgalloway

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冒签,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子钟病,更是在濱河造成了極大的恐慌萧恕,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肠阱,死亡現(xiàn)場(chǎng)離奇詭異票唆,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)屹徘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)走趋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人噪伊,你說(shuō)我怎么就攤上這事簿煌。” “怎么了鉴吹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵姨伟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我豆励,道長(zhǎng)夺荒,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮技扼,結(jié)果婚禮上伍玖,老公的妹妹穿的比我還像新娘。我一直安慰自己剿吻,他們只是感情好窍箍,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著和橙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪造垛。 梳的紋絲不亂的頭發(fā)上魔招,一...
    開(kāi)封第一講書(shū)人閱讀 52,255評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音五辽,去河邊找鬼办斑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛杆逗,可吹牛的內(nèi)容都是我干的乡翅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼罪郊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蠕蚜!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起悔橄,我...
    開(kāi)封第一講書(shū)人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤靶累,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后癣疟,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體挣柬,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年睛挚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了邪蛔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扎狱,死狀恐怖侧到,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淤击,我是刑警寧澤床牧,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站遭贸,受9級(jí)特大地震影響戈咳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一著蛙、第九天 我趴在偏房一處隱蔽的房頂上張望删铃。 院中可真熱鬧,春花似錦踏堡、人聲如沸猎唁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)秦忿。三九已至谎势,卻和暖如春境输,著一層夾襖步出監(jiān)牢的瞬間窃肠,已是汗流浹背蓝谨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工矢沿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留削樊,地道東北人豁生。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像漫贞,于是被迫代替她去往敵國(guó)和親甸箱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

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