Objective-C:我们应该在块内使用weak self还是在使用之前将weak self分配给strong?

4

我们知道,在块内使用strong self会导致保留循环和内存泄漏。常见做法是在块内使用weak self,或者将weak self分配给块内的strong self,然后在块内使用它,这样weak self在块执行期间不会被释放。由于weak self在任何情况下都会被清零,所以这是否重要呢?

4个回答

6

由于弱变量的易变性,您应该谨慎使用它们。如果您在多线程环境中使用弱变量,最好的做法是将弱变量赋值给一个强变量,然后在使用之前检查是否为nil。这样可以确保对象不会在方法执行中被释放,从而导致意外结果。

考虑以下情况:

__weak id var;

//...

if(var != nil)
{
    //var was released here on another thread and there are not more retaining references.
    [anotherObj performActionWithAnObjThatMustNotBeNil:var]; //<- You may crash here.
}

编译器可以配置为在对弱变量进行连续访问时发出警告。

另一方面,如果您的使用在主线程中,并且所有对该对象的调用都在主线程上,则此问题是无关紧要的,因为对象将在块调用之前或之后释放,因此直接访问弱变量是安全的。


1
这将确保在您使用期间对象不会被释放。对象不能在使用期间被释放,只能在使用前或使用后被释放。因此,如果您要么1)没有多个变量使用,要么2)不关心变量的不同用途获得不同的值,则这不是一个问题。 - newacct
2
@newacct 这不准确。当在代码中使用ARC时,它不会在使用过程中释放。然而,将对象传递给外部库时,在方法调用的过程中可能会释放该对象,从而引发问题。 - Léo Natan
2
@newacct 这并不正确,我的经验可以证明。进入方法并不能保留一个对象。 - Léo Natan
@newacct 再次强调,我的经验证明了相反的情况。 - Léo Natan
@newacct 谢谢。在多线程情况下,ARC规范会如何表现?如果变量可以在表达式结束时释放,从技术上讲,我们仍然应该将其分配给strong,以使其不被释放,对吗? - Boon
显示剩余15条评论

5

这里有两个容易混淆的可能问题:

__weak 引用是否可能在方法执行过程中变为 nil

id __strong strongObject = ...;
id __weak weakObject = strongObject;
dispatch_async(dispatch_get_main_queue(), ^{
  [weakObject method1]; // if weakObject is non-nil here
  [weakObject method2]; // can it become non-nil here?
});

是的!Xcode甚至会警告您。

如果在下面示例中调用了一个方法并且该方法在__weak lvalue上调用,那么self是否可能在方法执行过程中变为nil

id __strong strongObject = ...;
id __weak weakObject = strongObject;
dispatch_async(dispatch_get_main_queue(), ^{
  // is it possible for weakObject to be deallocated
  // while methodUsingSelf is being called?
  [weakObject methodUsingSelf];
});

- (void)methodUsingSelf {
  NSLog(@"%@", self);  // Could this be non-nil
  NSLog(@"%@", self);  // while this is nil?
}

不!苹果Swift团队的Joe Groff表示:

当self执行方法时,ObjC ARC会保证self一直存活。

Clang官方ARC文档在Semantics/Reading子节中涵盖了这种情况:

在对象lvalue上执行lvalue-to-rvalue转换时发生读取。

对于__weak对象,当前指针被保留,然后在当前完整表达式的末尾释放。这必须原子地与赋值和指针的最终释放同时执行。

因此,在__weak变量上调用方法大致相当于以下手动保留/释放(MRR)代码:

id retainedObject = ...;
id assignedObject = strongObject;
dispatch_async(dispatch_get_main_queue(), ^{
  {
    [assignedObject retain];
    [assignedObject methodUsingSelf];
    [assignedObject release];
  }
});    

当然,在MRR中,[assignedObject retain];可能会崩溃,因为assignedObject所指向的对象可能已被释放,所以assignedObject可能指向垃圾。ARC没有这个问题,因为它将weak引用置零

1
我认为,即使使用weak会起作用并且在需要的时间内被保留,但在使用前将其分配给strong将使其更易读和“无忧无虑”...:
__weak id weakThing = thing;
thing.someBlock = ^{
    if (weakThing) {
        id strongThing = weakThing;
        strongThing doThisWithThat...
    }
};

编译器不会报错,而且安全性高,也许更重要的是,对于明天尝试阅读这段代码的John Doe来说易于理解。

3
这几乎正确,不过我会在条件语句之前执行这个任务。这样就可以确保变量在检查和赋值之间不会被释放。 - Mark Stickley

0

你可以继续使用弱引用的self。唯一需要使用强引用的时候是当你尝试直接访问self->ivar而不是通过属性访问时。


这是半准确的,当尝试多次访问一个弱变量时,编译器确实会发出警告。 - Léo Natan
使用weak会有Leo提到的问题,即在您处于块中时它可能被释放,不是吗? - Boon
只有在其他地方没有保持对self的强引用时,才存在这种风险。大多数情况下,这并不是问题。例如,如果将UIView添加为子视图,则可以安全地访问weakSelf,因为superview.subviews数组会保持一个强指针。 - Nick
2
为什么要依赖于其他对象来为您保留强引用,当您可以轻松创建自己的强引用并消除那些不明显的依赖关系呢?强引用的目的是表明“我正在使用此对象,我不希望它消失”。因此,如果您不希望对象消失,请创建对其的强引用-不要依赖于其他对象。创建自己的强引用几乎不需要任何代价。 - Caleb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接