I'm trying to understand the memory leak situation in Swift language but there is a situation that I'm still wondering.
I've created a new UIViewController and call fetch function with storing the fetch task in a property without starting the task then I closed this UIViewController.
I found that the deinit function in this UIViewController is not called (Memory leak).
func fetchAPI() {
let url = URL(string: "https://www.google.com")!
let task = URLSession.shared.downloadTask(with: url) { _, _, _ in
DispatchQueue.main.async {
print(self.view.description)
}
}
self.vcTask = task
}
But If I call the fetch function with calling resume
method and then I close UIViewController again.
I found that the deinit function in this UIViewController is called (Memory not leak).
func fetchAPI() {
let url = URL(string: "https://www.google.com")!
let task = URLSession.shared.downloadTask(with: url) { _, _, _ in
DispatchQueue.main.async {
print(self.view.description)
}
}
self.vcTask = task
task.resume() // start downloading
}
For now I think that if I store a task in a property in UIViewController and I use self
in the callback. It would create a cycle that caused Memory leak.
But when I call task.resume()
Why the memory is not leak in this situation?
CodePudding user response:
An un-resume
d task will never execute its completion handler, because it will never complete. The task, and its handler, will therefore remain in memory.
We don't know the internal implementation of URLSession*
but it would seem sensible for the framework to discard completion handlers once they are executed. This would break the retain cycle and allow the view controller to be deallocated.
You could confirm this by adding extra logging in the completion handler and deinit method - I would expect the view controller not to be deallocated until the completion handler has run.
CodePudding user response:
(Adding to @jrturton's answer, which is 100% correct afaik)
This line of code
let task = URLSession.shared.downloadTask(with: url) { _, _, _ in ... }
captures self
strongly, causing the memory leak.
One way to avoid this is to change the capture to be weak
, like so:
let task = URLSession.shared.downloadTask(with: url) { [weak self] _, _, _ in
guard let self else { return }
DispatchQueue.main.async {
print(self.view.description)
}
}
Alternatively, try adding self.vcTask = nil
to the ViewController's viewDidDisappear
method to manually break the cycle.