Block中self的循環引用--摘自《禪與 Objective-C 編寫的藝術》的中文翻譯

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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容