【问题标题】:Is it safe to capture properties of `self` without capturing `self`?在不捕获“self”的情况下捕获“self”的属性是否安全?
【发布时间】:2020-03-20 22:04:09
【问题描述】:

您可以逐字复制这个游乐场:

var closures=[() -> Void]()
class Thing {
    let name: String
    init(_ name: String) { self.name=name }
}
class RefThingWrapper {
    let thing: Thing
    init(_ t: Thing) {
        self.thing=t
        closures.append { [thing, weak self] in   // Even though `thing` captured strongly, `self` could still get deallocated.
            print((self == nil) ? "`self` deallocated" : "`self` not deallocated")
            print(thing.name)   // Decided to use `thing` within closure to eliminate any chance of optimizations affecting the test results — which I found when I captured `self` strongly without using it.
        }
    }
}
struct ValThingWrapper {
    let thing: Thing
    init(_ t: Thing) {
        self.thing=t
        closures.append { [weak thing] in
            print((thing == nil) ? "`thing` deallocated" : "`thing` not deallocated")
        }
    }
}

var wrapper=Optional(RefThingWrapper(Thing("Thing 1")))   // Test with reference type.
//var wrapper=Optional(ValThingWrapper(Thing("Thing 1")))   // Test with value type.
closures[0]()
wrapper=nil
closures[0]()

它演示了self 的属性(self 是引用类型还是值类型)如何在闭包中独立于self 被捕获。按原样运行程序演示了在 self 被释放后存在的捕获属性。使用值类型包装器进行的测试表明,如果被弱捕获,一旦引用的值实例被释放,该实例将被释放。

我不确定这是否可行,因为一开始创建闭包时,我忘记初始化我正在捕获的属性。所以编译器抱怨——在捕获列表中——'self' used before all stored properties are initialized。所以我认为self 被隐式捕获,只有在深入挖掘之后才会发现。

这是否记录在某处?我找到了 Joe Groff 提出的this post

对于类的“让”属性,建议具有 默认情况下,闭包以这种方式直接捕获 属性 而不是捕获“自我”(并且可能允许引用它们 没有“自我”,因为“自我”不会参与形成的任何循环 这样)。

这是 2015 年的事,我没有发现讨论中产生的任何已实施的提案。是否有任何权威来源可以传达这种行为?

【问题讨论】:

  • 是的,将值预先捕获到捕获列表中的能力鲜为人知。请注意,这与闭包捕获不同;其实恰恰相反,是一种避免闭包捕获的方法。
  • 看看我使用这个的例子:github.com/mattneub/Programming-iOS-Book-Examples/blob/master/… 见第 41 行。如果我们没有预先捕获,我们将做与我正在尝试做的完全相反的事情。我们想要捕获self.center 现在的值,而不是当这个闭包被调用时它的值。
  • @matt 哇。不记得以前在捕获列表中看到过分配。查看语法,预先捕获一个值类型看起来大致是我想象的样子。使用引用类型并且没有 = 符号,self 是否被隐式捕获对我来说似乎模棱两可。但事后看来,如果您可以捕获一个值类型而不捕获 self 以及它,这似乎很自然,引用类型也将遵循同样的情况。

标签: swift


【解决方案1】:

如果您只是要求获取有关捕获列表和引用类型的文档,请参阅 Swift 编程语言 Resolving Strong Reference Cycles for Closures。另见Language Reference: Capture Lists

  • 如果您的捕获列表包含值类型,您将获得该值的副本。

    var foo = 1
    
    let closure = { [foo] in
        print(foo)
    }
    
    foo = 42
    
    closure() // 1; capturing value of `foo` as it was when the closure was declared
    
  • 如果您的捕获列表包含引用类型,您将获得对该当前实例的引用的副本。

    class Bar {
        var value: Int
    
        init(value: Int) { self.value = value }
    }
    
    var bar = Bar(value: 1)
    
    let closure = { [bar] in
        print(bar.value)
    }
    
    bar.value = 2
    
    bar = Bar(value: 3)
    
    closure() // 2; capturing reference to first instance that was subsequently updated
    

    默认情况下,这些捕获的引用是强引用,但也可以根据需要选择标记为 weakunowned

  • Capture Lists 文档概述了在不捕获 self 的情况下捕获属性的方法:

    您还可以将任意表达式绑定到捕获列表中的命名值。创建闭包时计算表达式,并以指定的强度捕获值。例如:

    // Weak capture of "self.parent" as "parent"
    myFunction { [weak parent = self.parent] in print(parent!.title) }
    

    我并不喜欢他们的代码示例,但它说明了在不捕获 self 的情况下捕获属性。

【讨论】:

    猜你喜欢
    • 2019-06-06
    • 2011-03-04
    • 2017-01-08
    • 2016-04-28
    • 1970-01-01
    • 2011-02-07
    • 1970-01-01
    • 2017-11-27
    • 2019-10-02
    相关资源
    最近更新 更多