I can't find any official documentation on this and there's mixed opinions out there.
In the following situation, all is well.
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
}
}
}
}
However, if I make the following change, I no longer need to use unowned self
, but I'm still concerned about capture self. Should I be concerned? If not, why?
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
}
}
}
}
When you assign a closure to tableViewHandler.didSelectRow
, you assign to it and retain whatever that closure captures.
self
is retaining tableViewHandler
.
Therefore, the danger is that you will refer to self
within the closure. If you do, that's a retain cycle.
And this might not be due to referring to self
explicitly. Any mention of a property or method of self
is an implicit reference to self
.
Okay, so with that out of the way, let's examine the closure.
You do not mention self
implicitly or explicitly in the body of the closure. However, you do call a local method, categorySelected
. Therefore, you capture this method.
And categorySelected
does mention self
. Therefore, it captures self
(because every function is a closure).
Thus there is a potential retain cycle and you should continue to say unowned self
to prevent a retain cycle.
(I presume that the compiler can't help you here by warning of the retain cycle; there's too much complexity. It's a problem you just have to solve by human reason.)
I did some investigations, and indeed you get a retain cycle if you use an inner function that references self
.
Here is an example:
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")
}
}
If you use setClosure1
(trivial) or setClosure2
(capture clause), no retain cycle occurs:
if (1==1) {
let t = Test()
t.setClosure1()
t.closure?()
} // deinit called here
but if you call setClosure3
, deinit
will not be called:
if (1==1) {
let t = Test()
t.setClosure3()
t.closure?()
} // deinit NOT called here
There is no direct way to solve this problem; as you can see, using [unowned self]
in the inner function results in an compiler error, and using it in setClosure3
results in a warning.
Nevertheless, there is a way to get around this issue - instead of using an inner function, you can use a second closure, in which you can specify the [unowned self]
capture clause:
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
Conclusion: Swift should allow capture clauses in innner functions.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.