### 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
网友评论