I am experiencing a leak with unowned self under conditions where, to the best of my knowledge, there shouldn't be a leak. Let me show an example, it is a little contrived, so bear with me, I've tried to make the simplest case I could.
Suppose I have a simple view controller that executes a closure on viewDidLoad:
class ViewController2: UIViewController {
var onDidLoad: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
onDidLoad?()
}
}
and a class, ViewHandler, that owns an instance of this view controller and injects a call to a notify function into its closure, using an unowned reference:
class ViewHandler {
private let viewController2 = ViewController2()
func getViewController() -> ViewController2 {
viewController2.onDidLoad = { [unowned self] in
self.notify()
}
return viewController2
}
func notify() {
print("My viewcontroller has loaded its view!")
}
}
Then, when its view controller is presented by another view controller, the ViewHandler is leaking when nilled out:
class ViewController: UIViewController {
private var viewHandler: ViewHandler?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
viewHandler = ViewHandler()
self.present(viewHandler!.getViewController(), animated: true, completion: nil)
viewHandler = nil // ViewHandler is leaking here.
}
}
I know the example may seem a little contrived, but as far as I know there shouldn't be a leak. Let my try and break it down:
Before presenting ViewHandler.ViewController2, ownership should look like this:
ViewController -> ViewHandler -> ViewController2 -|
^ |
|_ _ _ _ unowned _ _ _ _ _ |
After presenting ViewHandler.ViewController2, ownership should look like this:
_______________________________
| v
ViewController -> ViewHandler -> ViewController2 -|
^ |
|_ _ _ _ unowned _ _ _ _ _ |
After nilling out ViewHandler, ownership should look like this:
_______________________________
| v
ViewController ViewHandler -> ViewController2 -|
^ |
|_ _ _ _ unowned _ _ _ _ _ |
Nothing is owning ViewHandler and it should be released. However this is not the case and ViewHandler is leaking.
If I change the reference in the capture list of the closure injected into onDidLoad to weak, there is no leak and ViewHandler is released as expected:
func getViewController() -> ViewController2 {
viewController2.onDidLoad = { [weak self] in
self?.notify()
}
return viewController2
}
Also, something I can't explain, if I keep the reference as unowned and make ViewHandler inherit from NSObject, ViewHandler is released as expected and there is no leak:
class ViewHandler: NSObject {
private let viewController2 = ViewController2()
func getViewController() -> ViewController2 {
viewController2.onDidLoad = { [unowned self] in
self.notify()
}
return viewController2
}
....
}
Anyone who can explain what going on?