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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容