美文网首页
Block中self的循环引用--摘自《禅与 Objective

Block中self的循环引用--摘自《禅与 Objective

作者: 赫子丰 | 来源:发表于2018-07-19 13:30 被阅读12次

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

          本文链接:https://www.haomeiwen.com/subject/ozctmftx.html