我在这个嵌套函数中捕获自我吗?编译器不会发出警告
Am I capturing self in this nested function? The compiler does not fire a warning
我找不到关于此的任何官方文档,并且有不同的意见。
下面的情况,一切正常
final class MyVC: UIViewController {
var space: Space!
private let tableView = MenuCategoriesTableView()
private let tableViewHandler = MenuCategoriesTableViewHandler()
override func viewDidLoad() {
super.viewDidLoad()
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
tableView.dataSource = tableViewHandler
tableView.delegate = tableViewHandler
tableViewHandler.didSelectRow = { [unowned self] option in
let category = option.makeCategory()
if category.items.count > 0 {
let controller = MenuItemsViewController()
controller.title = option.rawValue
controller.space = self.space
self.show(controller, sender: self)
} else {
// whatever
}
}
}
}
但是,如果我进行以下更改,我就不再需要使用 unowned self
,但我仍然担心捕获自我。我应该担心吗?如果不是,为什么?
final class MyVC: UIViewController {
...etc...
override func viewDidLoad() {
super.viewDidLoad()
...etc...
func categorySelected(_ option: MenuOption, _ category: MenuCategory) {
let controller = MenuItemsViewController()
controller.title = option.rawValue
controller.space = space
show(controller, sender: self)
}
tableViewHandler.didSelectRow = { option in
let category = option.makeCategory()
if category.items.count > 0 {
categorySelected(option, category)
} else {
// whatever
}
}
}
}
当您将闭包分配给 tableViewHandler.didSelectRow
时,您分配给它并保留闭包捕获的任何内容。
self
正在保留 tableViewHandler
.
因此,危险在于您将在闭包中引用 self
。如果你这样做,那就是一个保留周期。
这可能不是因为明确引用了 self
。任何提及 属性 或 self
的方法都是对 self
.
的隐含引用
好吧,既然如此,让我们检查闭包。
您没有在闭包主体中隐式或显式提及 self
。但是,您确实调用了本地方法 categorySelected
。因此,你捕获这个方法。
并且categorySelected
确实提到了self
。因此,它捕获self
(因为每个函数都是闭包)。
因此存在潜在的保留循环,您应该继续说 unowned self
以防止保留循环。
(我认为编译器不能通过警告保留循环来帮助你;太复杂了。这是一个你只能通过人类理性来解决的问题。)
我做了一些调查,如果你使用引用 self
.
的内部函数,你确实会得到一个保留周期
这是一个例子:
typealias ClosureType = () -> ()
class Test {
var closure:ClosureType?
let value = 42
func setClosure1() {
self.closure = {
print ("from setClosure1")
}
}
func setClosure2() {
self.closure = {
[unowned self] in
let v = self.value
print ("from setClosure2 - value: \(v)")
}
}
func setClosure3() {
func innerFunc() {
// [unowned self] in // not allowed, compile error (sometimes even crashes)
let v = value
print ("value: \(v)")
}
self.closure = {
[unowned self] in // compiler warning: "Capture [self] was never used"
print ("from setClosure3")
innerFunc()
}
}
deinit {
print ("Deinit")
}
}
如果使用setClosure1
(平凡)或setClosure2
(捕获子句),则不会发生保留循环:
if (1==1) {
let t = Test()
t.setClosure1()
t.closure?()
} // deinit called here
但是如果你调用setClosure3
,deinit
将不会被调用:
if (1==1) {
let t = Test()
t.setClosure3()
t.closure?()
} // deinit NOT called here
没有直接的方法解决这个问题;如您所见,在内部函数中使用 [unowned self]
会导致编译器错误,而在 setClosure3
中使用会导致警告。
不过,有一种方法可以解决这个问题 - 您可以使用第二个闭包,而不是使用内部函数,您可以在其中 指定 [unowned self]
捕获子句:
func setClosure4() {
let innerClosure:ClosureType = {
[unowned self] in
let v = self.value
print ("value: \(v)")
}
self.closure = {
print ("from setClosure4")
innerClosure()
}
}
// ...
if (1==1) {
let t = Test()
t.setClosure4()
t.closure?()
} // deinit called here
结论:Swift 应该允许在内部函数中使用捕获子句。
我找不到关于此的任何官方文档,并且有不同的意见。
下面的情况,一切正常
final class MyVC: UIViewController {
var space: Space!
private let tableView = MenuCategoriesTableView()
private let tableViewHandler = MenuCategoriesTableViewHandler()
override func viewDidLoad() {
super.viewDidLoad()
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
tableView.dataSource = tableViewHandler
tableView.delegate = tableViewHandler
tableViewHandler.didSelectRow = { [unowned self] option in
let category = option.makeCategory()
if category.items.count > 0 {
let controller = MenuItemsViewController()
controller.title = option.rawValue
controller.space = self.space
self.show(controller, sender: self)
} else {
// whatever
}
}
}
}
但是,如果我进行以下更改,我就不再需要使用 unowned self
,但我仍然担心捕获自我。我应该担心吗?如果不是,为什么?
final class MyVC: UIViewController {
...etc...
override func viewDidLoad() {
super.viewDidLoad()
...etc...
func categorySelected(_ option: MenuOption, _ category: MenuCategory) {
let controller = MenuItemsViewController()
controller.title = option.rawValue
controller.space = space
show(controller, sender: self)
}
tableViewHandler.didSelectRow = { option in
let category = option.makeCategory()
if category.items.count > 0 {
categorySelected(option, category)
} else {
// whatever
}
}
}
}
当您将闭包分配给 tableViewHandler.didSelectRow
时,您分配给它并保留闭包捕获的任何内容。
self
正在保留 tableViewHandler
.
因此,危险在于您将在闭包中引用 self
。如果你这样做,那就是一个保留周期。
这可能不是因为明确引用了 self
。任何提及 属性 或 self
的方法都是对 self
.
好吧,既然如此,让我们检查闭包。
您没有在闭包主体中隐式或显式提及 self
。但是,您确实调用了本地方法 categorySelected
。因此,你捕获这个方法。
并且categorySelected
确实提到了self
。因此,它捕获self
(因为每个函数都是闭包)。
因此存在潜在的保留循环,您应该继续说 unowned self
以防止保留循环。
(我认为编译器不能通过警告保留循环来帮助你;太复杂了。这是一个你只能通过人类理性来解决的问题。)
我做了一些调查,如果你使用引用 self
.
这是一个例子:
typealias ClosureType = () -> ()
class Test {
var closure:ClosureType?
let value = 42
func setClosure1() {
self.closure = {
print ("from setClosure1")
}
}
func setClosure2() {
self.closure = {
[unowned self] in
let v = self.value
print ("from setClosure2 - value: \(v)")
}
}
func setClosure3() {
func innerFunc() {
// [unowned self] in // not allowed, compile error (sometimes even crashes)
let v = value
print ("value: \(v)")
}
self.closure = {
[unowned self] in // compiler warning: "Capture [self] was never used"
print ("from setClosure3")
innerFunc()
}
}
deinit {
print ("Deinit")
}
}
如果使用setClosure1
(平凡)或setClosure2
(捕获子句),则不会发生保留循环:
if (1==1) {
let t = Test()
t.setClosure1()
t.closure?()
} // deinit called here
但是如果你调用setClosure3
,deinit
将不会被调用:
if (1==1) {
let t = Test()
t.setClosure3()
t.closure?()
} // deinit NOT called here
没有直接的方法解决这个问题;如您所见,在内部函数中使用 [unowned self]
会导致编译器错误,而在 setClosure3
中使用会导致警告。
不过,有一种方法可以解决这个问题 - 您可以使用第二个闭包,而不是使用内部函数,您可以在其中 指定 [unowned self]
捕获子句:
func setClosure4() {
let innerClosure:ClosureType = {
[unowned self] in
let v = self.value
print ("value: \(v)")
}
self.closure = {
print ("from setClosure4")
innerClosure()
}
}
// ...
if (1==1) {
let t = Test()
t.setClosure4()
t.closure?()
} // deinit called here
结论:Swift 应该允许在内部函数中使用捕获子句。