您的问题有几个小问题,很难清楚地回答,但我想我理解潜在的担忧,简短的回答是“不”。但是你的例子是不可能的,所以答案是“不可能的”。如果可能的话,就没有强有力的参考(也不需要一个),所以问题仍然是“不可能的”。尽管如此,让我们来看看这里发生了什么。
首先,closure 不能引用 localString,除非它在 doStuff() 内的评论中以某种方式重新分配。 closure 分配在localString 不在范围内的级别。闭包只能在分配时捕获范围内的变量,而不是在调用它们时捕获。但是让我们回到这个问题的原始版本,在它被编辑之前。那个版本确实有你描述的情况:
@objc final myClass : NSObject
{
let classPropertyString = "A class property"
func doStuff()
{
let localString = "An object local to this function"
DispatchQueue.global(qos: .userInitiated).async { [classPropertyString] in // (1)
// Do things with 'classPropertyString' and 'localString'
}
// (2)
}
}
这里没有问题。 classPropertyString 被复制到闭包中,避免了任何保留循环。 localString 被闭包引用,因此只要闭包存在,它就会被保留。
因为您在捕获列表中列出了classPropertyString,所以在点 (1) 处对其进行评估并复制到闭包中。因为您隐式捕获了localString,所以它被视为参考。请参阅 Swift Programming Language Reference 中的 Capture Lists,了解一些出色的示例,了解它在不同情况下是如何工作的。
在任何情况下,(*) Swift 都不会允许你在闭包中使用的东西的底层存储消失在你背后。这就是为什么典型的问题是过度保留(内存泄漏)而不是悬空引用(崩溃)。
(*) “无论如何”这是一个谎言。 Swift 允许它有多种方式,但几乎所有方式都涉及“不安全”,这是您对此的警告。主要的例外是unowned,当然还有任何涉及! 类型的东西。而且 Swift 通常不是线程安全的,所以你需要小心...
关于线程安全的最后一条评论是隐式和显式捕获之间的细微区别非常重要的地方。考虑一下在两个队列上修改隐式捕获值的情况:
func doStuff() -> String
{
var localString = "An object local to this function"
DispatchQueue.global(qos: .userInitiated).async {
localString = "something else"
callFunction(localString)
}
localString = "even more changes"
return localString
}
在这种情况下会发生什么?好伤心,永远不要那样做。我相信这是未定义的行为,并且 localString 可能是 任何东西,包括损坏的内存,至少在最一般的情况下(它可能是调用 .async 的已定义行为;我我不确定)。但不要这样做。
但是对于您的正常情况,没有理由显式捕获局部变量。 (有时我希望 Swift 采用 C++ 的方式并说它是必需的,但事实并非如此。)
好的,隐式和显式的另一种不同方式可能会推动它们的工作方式。考虑这样的有状态闭包(我经常构建这些):
func incrementor() -> () -> Int {
var n = 0
return {
n += 1
return n
}
}
let inc = incrementor()
inc() // 1
inc() // 2
inc() // 3
let inc2 = incrementor()
inc2() // 1
看看局部变量n是如何被闭包捕获的,并且可以在它超出范围后进行修改。看看inc2 是如何拥有自己的局部变量版本的。现在尝试使用显式捕获。
func incrementor() -> () -> Int {
var n = 0
return { [n] in // <---- add [n]
n += 1 // Left side of mutating operator isn't mutable: 'n' is an immutable capture
return n
}
}
显式捕获是副本,它们是不可变的。隐式捕获是引用,因此与它们引用的事物具有相同的可变性。