美文网首页
self循环引用相关(Zen and the Art of th

self循环引用相关(Zen and the Art of th

作者: test158888 | 来源:发表于2017-06-29 21:09 被阅读40次

    一些关键点:

    block 是在栈上创建的
    block 可以复制到堆上
    Block会捕获栈上的变量(或指针),将其复制为自己私有的const(变量)。
    (如果在Block中修改Block块外的)栈上的变量和指针,那么这些变量和指针必须用__block关键字申明(译者注:否则就会跟上面的情况一样只是捕获他们的瞬时值)。
    如果 block 没有在其他地方被保持,那么它会随着栈生存并且当栈帧(stack frame)返回的时候消失。仅存在于栈上时,block对对象访问的内存管理和生命周期没有任何影响。

    如果 block 需要在栈帧返回的时候存在,它们需要明确地被复制到堆上,这样,block 会像其他 Cocoa 对象一样增加引用计数。当它们被复制的时候,它会带着它们的捕获作用域一起,retain 他们所有引用的对象。

    如果一个 block引用了一个栈变量或指针,那么这个block初始化的时候会拥有这个变量或指针的const副本,所以(被捕获之后再在栈中改变这个变量或指针的值)是不起作用的。(译者注:所以这时候我们在block中对这种变量进行赋值会编译报错:Variable is not assignable(missing __block type specifier),因为他们是副本而且是const的.具体见下面的例程)。

    当一个 block 被复制后,__block 声明的栈变量的引用被复制到了堆里,复制完成之后,无论是栈上的block还是刚刚产生在堆上的block(栈上block的副本)都会引用该变量在堆上的副本。

    ...
    CGFloat blockInt = 10;
    void (^playblock)(void) = ^{
        NSLog(@"blockInt = %zd", blockInt);
    };
    blockInt ++;
    playblock();
    ...
    
    //结果为:blockInt = 10
    

    最重要的事情是 __block 声明的变量和指针在 block 里面是作为显示操作真实值/对象的结构来对待的。

    block 在 Objective-C 的 runtime(运行时) 里面被当作一等公民对待:他们有一个 isa 指针,一个类也是用 isa 指针在Objective-C 运行时来访问方法和存储数据的。在非 ARC 环境肯定会把它搞得很糟糕,并且悬挂指针会导致 crash。__block 仅仅对 block 内的变量起作用,它只是简单地告诉 block:

    嗨,这个指针或者原始的类型依赖它们在的栈。请用一个栈上的新变量来引用它。我是说,请对它进行双重解引用,不要 retain 它。 谢谢,哥们。
    如果在定义之后但是 block 没有被调用前,对象被释放了,那么 block 的执行会导致 crash。 __block 变量不会在 block 中被持有,最后... 指针、引用、解引用以及引用计数变得一团糟。

    self 的循环引用

    当使用代码块和异步分发的时候,要注意避免引用循环。 总是使用 weak 来引用对象,避免引用循环。(译者注:这里更为优雅的方式是采用影子变量@weakify/@strongify 这里有更为详细的说明) 此外,把持有 block 的属性设置为 nil (比如 self.completionBlock = nil) 是一个好的实践。它会打破 block 捕获的作用域带来的引用循环。

    例子:

    __weak __typeof(self) weakSelf = self;
    [self executeBlock:^(NSData *data, NSError *error) {
    [weakSelf doSomethingWithData:data];
    }];
    不要这样:

    [self executeBlock:^(NSData *data, NSError *error) {
    [self doSomethingWithData:data];
    }];
    多个语句的例子:

    __weak __typeof(self)weakSelf = self;
    [self executeBlock:^(NSData *data, NSError *error) {
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
    [strongSelf doSomethingWithData:data];
    [strongSelf doSomethingWithData:data];
    }
    }];
    不要这样:

    __weak __typeof(self)weakSelf = self;
    [self executeBlock:^(NSData *data, NSError *error) {
    [weakSelf doSomethingWithData:data];
    [weakSelf doSomethingWithData:data];
    }];
    你应该把这两行代码作为 snippet 加到 Xcode 里面并且总是这样使用它们。

    __weak __typeof(self)weakSelf = self;
    __strong __typeof(weakSelf)strongSelf = weakSelf;
    这里我们来讨论下 block 里面的 self 的 __weak 和 __strong 限定词的一些微妙的地方。简而言之,我们可以参考 self 在 block 里面的三种不同情况。

    直接在 block 里面使用关键词 self
    在 block 外定义一个 __weak 的 引用到 self,并且在 block 里面使用这个弱引用
    在 block 外定义一个 __weak 的 引用到 self,并在在 block 内部通过这个弱引用定义一个 __strong 的引用。
    方案 1. 直接在 block 里面使用关键词 self

    如果我们直接在 block 里面用 self 关键字,对象会在 block 的定义时候被 retain,(实际上 block 是 copied 但是为了简单我们可以忽略这个)。一个 const 的对 self 的引用在 block 里面有自己的位置并且它会影响对象的引用计数。如果这个block被其他的类使用并且(或者)彼此间传来传去,我们可能想要在 block 中保留 self,就像其他在 block 中使用的对象一样. 因为他们是block执行所需要的.

    dispatch_block_t completionBlock = ^{
    NSLog(@"%@", self);
    }

    MyViewController *myController = [[MyViewController alloc] init...];
    [self presentViewController:myController
    animated:YES
    completion:completionHandler];
    没啥大不了。但是如果通过一个属性中的 self 保留 了这个 block(就像下面的例程一样),对象( self )保留了 block 会怎么样呢?

    self.completionHandler = ^{
    NSLog(@"%@", self);
    }

    MyViewController *myController = [[MyViewController alloc] init...];
    [self presentViewController:myController
    animated:YES
    completion:self.completionHandler];
    这就是有名的 retain cycle, 并且我们通常应该避免它。这种情况下我们收到 CLANG 的警告:

    Capturing 'self' strongly in this block is likely to lead to a retain cycle (在 block 里面发现了 self 的强引用,可能会导致循环引用)
    所以 __weak 就有用武之地了。

    方案 2. 在 block 外定义一个 __weak 的 引用到 self,并且在 block 里面使用这个弱引用

    这样会避免循坏引用,也是通常情况下我们的block作为类的属性被self retain 的时候会做的。

    __weak typeof(self) weakSelf = self;
    self.completionHandler = ^{
    NSLog(@"%@", weakSelf);
    };

    MyViewController *myController = [[MyViewController alloc] init...];
    [self presentViewController: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 文档 中表示 "为了 non-trivial cycles ,你应该这样" :

    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) 讨论的 一 些 的 在线 参考, Matt Galloway 的 (Effective Objective-C 2.0 和 Pro Multithreading and Memory Management for iOS and OS X ,大多数开发者始终没有弄清楚概念。

    在 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)。

    __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 不能正确执行前已经返回了。

    __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条件中,如果尝试用 -> 符号访问一个实例变量,编译器会给出非常清晰的错误信息:

    Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to a strong variable first. (对一个 __weak 指针的解引用不允许的,因为可能在竞态条件里面变成 null, 所以先把他定义成 strong 的属性)
    可以用下面的代码展示

    __weak typeof(self) weakSelf = self;
    myObj.myBlock = ^{
    id localVal = weakSelf->someIVar;
    };
    在最后

    方案 1: 只能在 block 不是作为一个 property 的时候使用,否则会导致 retain cycle。

    方案 2: 当 block 被声明为一个 property 的时候使用。

    方案 3: 和并发执行有关。当涉及异步的服务的时候,block 可以在之后被执行,并且不会发生关于 self 是否存在的问题。

    相关文章

      网友评论

          本文标题:self循环引用相关(Zen and the Art of th

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