Swift关闭:捕获列表必须详尽吗?
假设我有一个这样的Swift类:Swift关闭:捕获列表必须详尽吗?,swift,memory,memory-management,closures,Swift,Memory,Memory Management,Closures,假设我有一个这样的Swift类: @objc final MyClass : NSObject { let classPropertyString = "A class property" func doStuff() { let localString = "An object local to this function" DispatchQueue.global(qos: .userInitiated).async { [cl
@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
// Do things with 'classPropertyString' and 'localString'
}
}
}
我的问题是:当我写一个捕获列表时,我是否有责任详尽地列出所有我希望闭包包含强引用的东西
换句话说,如果我从捕获列表中省略了
localString
(就像我在这里所做的那样),那么闭包是否仍然会自动捕获对它的强烈引用,或者我是否处于一个糟糕的时期?您的问题有一些小的怪癖,使得很难清楚地回答,但我想我理解潜在的问题,简短的回答是“不可能”。但是你的例子是不可能的,所以答案是“不可能”。如果可能的话,就没有强有力的参考(也没有必要),所以问题仍然是“不可能”。即使如此,我们还是来看看这里发生了什么
首先,closure
不能引用localString
,除非在doStuff()
内的注释中以某种方式重新分配它<代码>闭包在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
被复制到闭包中,避免任何保留循环<代码>本地字符串被闭包引用,因此只要闭包存在,它就会被保留
因为您在捕获列表中列出了classPropertyString
,所以会在点(1)处对其求值并复制到闭包中。因为您隐式捕获了localString
,所以它被视为引用。请参阅Swift编程语言参考中的一些优秀示例,这些示例确切地说明了这在不同情况下是如何工作的
在任何情况下(*)都不会允许您在闭包中使用的东西的底层存储在背后消失。这就是为什么典型的问题是过多的保留(内存泄漏),而不是挂起引用(崩溃)
(*)“无论如何”这是一个谎言。斯威夫特有几种方法可以做到这一点,但几乎所有的方法都涉及“不安全”,这是你对此的警告。主要的例外是无主
,当然还有任何涉及的东西代码>类型。而且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++的方式,说它是必需的,但它不是。)
好吧,还有一种方式是隐式和显式的,它们是不同的,这可能会让我们明白它们是如何工作的。考虑像这样的状态关闭(我经常这样做):
查看闭包如何捕获局部变量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
}
}
func incrementor()->()->Int{
var n=0
在//中返回{[n],尽管苹果公司在其文档中没有讨论“定义捕获列表中应强烈捕获的项目”方法(),我在不同的地方看到过它,我想知道它的语义。因此,这个问题。我RTFM.与您的问题无关,但它是以大写字母开始命名所有类的快速命名约定。嗯…问题:在第一个示例中,指定[classPropertyString]在捕获列表中,它实际上会复制值,因为它是实例化闭包的时间点。但是,据我所知,不会复制localString,而是通过引用语义绑定到闭包,因为它没有指定为显式捕获。这与它是值类型这一事实无关(参见第一个n示例)事实上,它是一个let常量。感谢您进行了非常全面的演练;这真是太棒了。尽管我理解了为什么使用GCD不会创建一个保留周期,但我还是回到了原来的示例。正如您所知,我是一名长期的C/ObjC开发人员,正在向Swift过渡,所以我仍然对细节有自己的看法。@Ham谢谢你的评论。我已经编辑过(大部分只是删除了不准确的句子。)谢谢@MichaelLong.edited。
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
}
}