我注意到在 Swift 2.2 中,使用 @noescape
标记为非转义的闭包不需要显式的 self
。在 Swift 3 中,默认情况下所有闭包都是非转义的,现在如果您希望它们能够转义,则需要使用 @escaping
标记它们。
鉴于默认情况下 Swift 3 中的所有闭包都是非转义的,为什么它们需要显式的 self
?
final class SomeViewController: NSViewController {
var someClosure: () -> () = { _ in }
override func viewDidLoad() {
super.viewDidLoad()
someClosure = {
view.layer = CALayer() // ERROR: Implicit use of `self` in closure; use `self.` to make capture semantics explicit
}
}
}
最佳答案
In Swift 3, all closures are non-escaping by default
不,在 Swift 3 中,只有闭包函数参数(即本身就是函数的函数输入)在默认情况下是非转义的(根据 SE-0103 )。例如:
class A {
let n = 5
var bar : () -> Void = {}
func foo(_ closure: () -> Void) {
bar = closure // As closure is non-escaping, it is illegal to store it.
}
func baz() {
foo {
// no explict 'self.' required in order to capture n,
// as foo's closure argument is non-escaping,
// therefore n is guaranteed to only be captured for the lifetime of foo(_:)
print(n)
}
}
}
作为closure
在上面的例子中是非转义的,禁止被存储或捕获,从而将其生命周期限制为函数的生命周期 foo(_:)
.因此,这意味着它捕获的任何值都保证在函数退出后不会保持捕获状态——这意味着您无需担心捕获时可能发生的问题,例如保留周期。
但是,闭包存储的属性(例如上例中的 bar
)根据定义转义(用 @noescape
标记它是荒谬的)作为其生命周期 不 限于给定的函数——只要给定的实例保留在内存中,它(以及因此捕获的所有变量)就会保留在内存中。因此,这很容易导致诸如保留循环之类的问题,这就是为什么您需要使用显式 self.
的原因。为了使捕获语义明确。
事实上,举例来说,您的示例代码将在 viewDidLoad()
上创建一个保留周期。被称为someClosure
强烈捕获 self
, 和 self
强烈引用someClosure
,因为它是一个存储属性。
值得注意的是,作为“存储函数属性总是转义”规则的扩展,存储在聚合中的函数(即具有关联值的结构和枚举)也总是转义,因为对您的操作没有任何限制聚合体。作为pointed out by pandaren codemaster ,这个当前包括Optional
– 意思是 Optional<() -> Void>
(又名 (() -> Void)?
)总是在转义。不过,编译器最终可能会将其作为函数参数的特例,因为可选的已经建立在很多编译器的魔法之上。
当然,您希望能够使用 @noescape
的地方attribute 在闭包上,闭包是函数中的局部变量。这样的闭包将具有可预测的生命周期,只要它不存储在函数外部或被捕获。例如:
class A {
let n = 5
func foo() {
let f : @noescape () -> Void = {
print(n)
}
f()
}
}
不幸的是,作为@noescape
在 Swift 3 中被删除,这是不可能的(有趣的是,在 Xcode 8 GM 中,这是可能的,但会产生弃用警告)。作为Jon Shier says ,我们将不得不等待它被重新添加到语言中,这可能发生也可能不会发生。
关于swift - 为什么闭包在 Swift 3 中默认都是非转义的而需要显式的 `self`?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39433221/