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

你可能感兴趣的:(Block中self的循环引用--摘自《禅与 Objective-C 编写的艺术》的中文翻译)