###? 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