为什么将弱引用传递给块会阻止保留对象?

Why passing a weak reference to a block prevents an object from being retained?

我们都知道块会保留它们捕获的对象。我们也知道我们可以通过将对象的弱引用传递到块中来避免这种情况。但为什么会这样呢?保留一个对象意味着将其保留计数增加一个。为什么传递弱引用会有所不同?无论是弱还是强,它仍然指向同一个对象,并且这个对象的保留计数将逐块增加。我对吗?那么,如果我们将弱引用传递给块内的对象,为什么对象的保留计数不会增加呢?它在内部是如何工作的?

您可能想了解闭包的一般工作原理。

以下面的代码为例,

var name = "NSNoName"
NSLog("Original name: %@ <%p>", name, name)

let takeName: (String) -> Void -> () = {
    name in
    return {
        NSLog("Name inside block: %@ <%p>", name, name)
    }
}

let  returnName = takeName(name)
name = "NSNoFame"
returnName()

NSLog("Changed name: %@ <%p>", name, name)

最初,name变量的值为"NSNoName"。这时候打印名字的时候,得到的结果是,

Original name: NSNoName <0x7f8fb86004a0>

我有一个简单的闭包,它将字符串作为参数。我用同名对象调用闭包,结果块创建了自己的对象副本。然后,我继续更改 name 对象,现在,如果我调用该块来打印名称,该块具有传递给它的相同原始值。但是,对象是不同的,这意味着该块创建了一个具有相同值的新对象。

Name inside block: NSNoName <0x7f8fb8602510>

最后一个 NSLog,打印了一个不同的值,因为它已经被改变并且有一些不同的值,

Changed name: NSNoFame <0x7f8fb8603ae0>

这就是你要告诉一个块,创建对象的弱引用的原因,这意味着如果原始对象不再存在,nil在块内创建的引用对象。

虽然 Objective C,但似乎有点不同,

@interface TestViewController ()

@property (nonatomic, strong) NSString *name;

@end

@implementation TestViewController


- (void)viewDidLoad
{
    [super viewDidLoad];

    self.name = @"NSNoName";

    NSLog(@"Original name: %@ <%p>", self.name, self.name);

    typedef void(^ReturnNameBlock)();

    ReturnNameBlock (^takeName)(NSString*) = ^ReturnNameBlock(NSString *name) {
        return ^{
            NSLog(@"Name inside block: %@ <%p>", name, name);
        };
    };

    ReturnNameBlock returnName = takeName(self.name);

    self.name = @"NSNoFame";

    returnName();

    NSLog(@"Changed name: %@ <%p>", self.name, self.name);

}

@end

我的日志是这样的,

Original name: NSNoName <0x103ae34c0>
Name inside block: NSNoName <0x103ae34c0>
Changed name: NSNoFame <0x103ae3520>

如果您查看日志,该块拥有原始 self.name 对象,因为两者具有相同的内存地址。虽然 viewController 不再拥有它,但是当我们更改 self.name = "NSNoFame" 时,该块仍然保留对象的相同实例。

swift和objective的区别在于,Objective C块保留传递给它的对象的原始实例,而swift闭包创建一个副本原始实例变量。

弱引用不会增加保留计数,弱引用只是指向对象的指针,如果对象不再存在,那么弱引用属性设置为nil,ARC处理这个。我不相信对象保留计数会因块内的弱引用而增加。

Because a weak reference does not keep a strong hold on the instance it refers to, it is possible for that instance to be deallocated while the weak reference is still referring to it. Therefore, ARC automatically sets a weak reference to nil when the instance that it refers to is deallocated.

文档 link:https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html

至于关于 ARC 如何与块一起工作的具体信息,我从 apple 找到了这个,这对你的问题没有太大帮助: https://developer.apple.com/library/ios/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html

但是,本段可能有助于理解块如何保留其局部变量:

Blocks are called closures in other languages such as Python, Ruby and Lisp, because they encapsulate state when they are declared. A block creates a const copy of any local variable that is referenced inside of its scope.

发件人:http://www.raywenderlich.com/9438/how-to-use-blocks-in-ios-5-tutorial-part-2

你可以把块想象成一个对象,每个捕获的变量都有一个 "instance variable",在创建块时用相应捕获变量的值初始化。

在 ARC 中,块的 "instance variables" 与相应的捕获变量具有相同的所有权说明符。因此,如果捕获的对象指针类型的变量是 __strong(默认值),则块的 "instance variable" 也是 __strong,因此它会在块的生命周期内保留指向的对象。如果捕获的对象指针类型的变量是__strong,块的"instance variable"也是__weak,因此它是指向对象的归零弱引用。