【问题标题】:Why passing a weak reference to a block prevents an object from being retained?为什么传递对块的弱引用会阻止对象被保留?
【发布时间】:2015-01-08 19:31:01
【问题描述】:

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

【问题讨论】:

    标签: objective-c automatic-ref-counting objective-c-blocks reference-counting


    【解决方案1】:

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

    因为弱引用不会对其所引用的实例保持强控制,所以当弱引用仍在引用它时,该实例可能会被释放。因此,当 ARC 引用的实例被释放时,ARC 会自动设置一个对 nil 的弱引用。

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

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

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

    块在 Python、Ruby 和 Lisp 等其他语言中称为闭包,因为它们在声明时封装了状态。块创建其范围内引用的任何局部变量的 const 副本。

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

    【讨论】:

      【解决方案2】:

      您可以将块视为一个对象,其中每个捕获的变量都有一个“实例变量”,该对象在创建块时使用相应的捕获变量的值进行初始化。

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

      【讨论】:

        【解决方案3】:

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

        以下面的代码为例,

        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 闭包创建原始实例变量的副本。

        【讨论】:

        • 这似乎是 Swift 代码; OP 在询问 ObjC。
        • @JoshCaswell 看起来并没有太大的不同,但是我用 Objective C 块得出了一个结论。
        猜你喜欢
        • 1970-01-01
        • 2014-05-29
        • 1970-01-01
        • 2014-05-27
        • 1970-01-01
        • 1970-01-01
        • 2015-04-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多