###? self的循環引用
當使用代碼塊和異步分發的時候,要注意避免引用循環。總是使用`weak`來引用對象,避免引用循環。(譯者注:這里更為優雅的方式是采用影子變量@weakify/@strongify[這里有更為詳細的說明](https://github.com/jspahrsummers/libextobjc/blob/master/extobjc/EXTScope.h))此外,把持有block的屬性設置為nil (比如`self.completionBlock = nil`)是一個好的實踐。它會打破block捕獲的作用域帶來的引用循環。
**例子:**
```objective-c
__weak __typeof(self) weakSelf =self;
[selfexecuteBlock:^(NSData *data, NSError *error) {
? ? [weakSelf doSomethingWithData:data];
}];
```
**不要這樣:**
```objective-c
[selfexecuteBlock:^(NSData *data, NSError *error) {
? ? [selfdoSomethingWithData:data];
}];
```
**多個語句的例子:**
```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];
}];
```
你應該把這兩行代碼作為 snippet 加到 Xcode 里面并且總是這樣使用它們。
```objective-c
__weak __typeof(self)weakSelf =self;
__strong __typeof(weakSelf)strongSelf = weakSelf;
```
這里我們來討論下block里面的self的`__weak`和`__strong`? 限定詞的一些微妙的地方。簡而言之,我們可以參考self在block里面的三種不同情況。
1.直接在block里面使用關鍵詞self
2.在block外定義一個`__weak`的引用到self,并且在block里面使用這個弱引用
3.在block外定義一個`__weak`的引用到self,并在在block內部通過這個弱引用定義一個`__strong`? 的引用。
**方案 1. 直接在 block 里面使用關鍵詞 `self`**
如果我們直接在 block 里面用 self 關鍵字,對象會在 block 的定義時候被 retain,(實際上 block 是[copied][blocks_caveat13]? 但是為了簡單我們可以忽略這個)。一個 const 的對 self 的引用在 block 里面有自己的位置并且它會影響對象的引用計數。如果這個block被其他的類使用并且(或者)彼此間傳來傳去,我們可能想要在 block 中保留 self,就像其他在 block 中使用的對象一樣. 因為他們是block執行所需要的.
```objective-c
dispatch_block_t completionBlock = ^{
? ? NSLog(@"%@",self);
}
MyViewController *myController = [[MyViewController alloc] init...];
[selfpresentViewController:myController
?? ? ? ? ? ? ? ? ? animated:YES
?? ? ? ? ? ? ? ? completion:completionHandler];
```
沒啥大不了。但是如果通過一個屬性中的 `self` 保留 了這個 block(就像下面的例程一樣),對象( self )保留了 block 會怎么樣呢?
```objective-c
self.completionHandler = ^{
? ? NSLog(@"%@",self);
}
MyViewController *myController = [[MyViewController alloc] init...];
[selfpresentViewController:myController
?? ? ? ? ? ? ? ? ? animated:YES
?? ? ? ? ? ? ? ? completion:self.completionHandler];
```
這就是有名的 retain cycle, 并且我們通常應該避免它。這種情況下我們收到 CLANG 的警告:
```objective-c
Capturing 'self' strongly inthisblock is likely to lead to a retain cycle(在block里面發現了`self`的強引用,可能會導致循環引用)
```
所以`__weak`就有用武之地了。
**方案 2. 在 block 外定義一個 `__weak` 的 引用到 self,并且在 block 里面使用這個弱引用**
這樣會避免循壞引用,也是通常情況下我們的block作為類的屬性被self retain 的時候會做的。
```objective-c
__weak typeof(self) weakSelf =self;
self.completionHandler = ^{
? ? NSLog(@"%@", weakSelf);
};
MyViewController *myController = [[MyViewController alloc] init...];
[selfpresentViewController:myController
?? ? ? ? ? ? ? ? ? animated:YES
?? ? ? ? ? ? ? ? completion:self.completionHandler];
```
這個情況下 block 沒有 retain 對象并且對象在屬性里面 retain 了 block 。所以這樣我們能保證了安全的訪問 self。 不過糟糕的是,它可能被設置成 nil 的。問題是:如何讓 self 在 block 里面安全地被銷毀。
考慮這么個情況:block 作為屬性(property)賦值的結果,從一個對象被復制到另一個對象(如 myController),在這個復制的 block 執行之前,前者(即之前的那個對象)已經被解除分配。
下面的更有意思。
**方案 3. 在 block 外定義一個 `__weak` 的 引用到 self,并在在 block 內部通過這個弱引用定義一個`__strong`? 的引用**
你可能會想,首先,這是避免retain cycle? 警告的一個技巧。
這不是重點,這個self的強引用是在block執行時被創建的,但是否使用self在block定義時就已經定下來了,因此self (在block執行時)會被retain.
[Apple文檔][blocks_caveat1]中表示"為了non-trivial cycles,你應該這樣":
```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...
? ? }
};
```
首先,我覺得這個例子看起來是錯誤的。如果block本身在completionHandler屬性中被retain了,那么self如何被delloc和在block之外賦值為nil呢? completionHandler屬性可以被聲明為? `assign`或者`unsafe_unretained`的,來允許對象在block被傳遞之后被銷毀。
我不能理解這樣做的理由,如果其他對象需要這個對象(self),block 被傳遞的時候應該 retain 對象,所以 block 應該不被作為屬性存儲。這種情況下不應該用 `__weak`/`__strong`
總之,其他情況下,希望 weakSelf 變成 nil 的話,就像第二種情況解釋那么寫(在 block 之外定義一個弱應用并且在 block 里面使用)。
還有,Apple的 "trivial block" 是什么呢。我們的理解是 trivial block 是一個不被傳送的 block ,它在一個良好定義和控制的作用域里面,weak 修飾只是為了避免循環引用。
雖然有 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],大多數開發者始終沒有弄清楚概念。
在 block 內用強引用的優點是,搶占執行的時候的魯棒性。在 block 執行的時候, 再次溫故下上面的三個例子:
**方案 1. 直接在 block 里面使用關鍵詞 `self`**
如果block被屬性retain,self和block之間會有一個循環引用并且它們不會再被釋放。如果block被傳送并且被其他的對象copy了,self在每一個copy里面被retain
**方案 2. 在 block 外定義一個 `__weak` 的 引用到 self,并且在 block 里面使用這個弱引用**
不管 block 是否通過屬性被 retain ,這里都不會發生循環引用。如果 block 被傳遞或者 copy 了,在執行的時候,weakSelf 可能已經變成 nil。
block 的執行可以搶占,而且對 weakSelf 指針的調用時序不同可以導致不同的結果(如:在一個特定的時序下 weakSelf 可能會變成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 外定義一個 `__weak` 的 引用到 self,并在在 block 內部通過這個弱引用定義一個`__strong`? 的引用。**
不管 block 是否通過屬性被 retain ,這里也不會發生循環引用。如果 block 被傳遞到其他對象并且被復制了,執行的時候,weakSelf 可能被nil,因為強引用被賦值并且不會變成nil的時候,我們確保對象 在 block 調用的完整周期里面被 retain了,如果搶占發生了,隨后的對 strongSelf 的執行會繼續并且會產生一樣的值。如果 strongSelf 的執行到 nil,那么在 block 不能正確執行前已經返回了。
```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(搶占的時候,strongSelf還是非nil的)
? ? ? [strongSelf doSomethingElse];// strongSelf != nil
? ? }
? ? else{
? ? ? ? // Probably nothing...
? ? ? ? return;
? ? }
};
```
在ARC條件中,如果嘗試用 `->` 符號訪問一個實例變量,編譯器會給出非常清晰的錯誤信息:
```objective-c
Dereferencing a __weak pointer is not allowed due to possiblenullvalue caused by race condition, assign it to a strong variable first. (對一個__weak指針的解引用不允許的,因為可能在競態條件里面變成null,所以先把他定義成strong的屬性)
```
可以用下面的代碼展示
```objective-c
__weak typeof(self) weakSelf =self;
myObj.myBlock= ^{
? ? id localVal = weakSelf->someIVar;
};
```
在最后
***方案 1**:只能在block不是作為一個property的時候使用,否則會導致retain cycle。
***方案 2**:? 當block被聲明為一個property的時候使用。
* **方案 3**: 和并發執行有關。當涉及異步的服務的時候,block 可以在之后被執行,并且不會發生關于 self 是否存在的問題。
[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