我正在尝试了解 Swift 语言中的内存泄漏情况,但有一种情况我仍然想知道。
我创建了一个新的 UIViewController 并调用 fetch 函数,将获取任务存储在属性中而不启动任务,然后我关闭了这个 UIViewController。
我发现这个UIViewController中的deinit函数没有被调用(内存泄漏)。
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
}
但是如果我通过调用
resume
方法调用 fetch 函数,然后再次关闭 UIViewController。
我发现这个UIViewController中的deinit函数被调用了(内存不泄漏)。
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
}
目前我认为,如果我将任务存储在 UIViewController 的属性中,并在回调中使用
self
。它会创建一个导致内存泄漏的循环。
但是当我调用
task.resume()
时,为什么在这种情况下内存没有泄漏?
未完成的任务将永远不会执行其完成处理程序,因为它永远不会完成。因此,任务及其处理程序将保留在内存中。
我们不知道resume
的内部实现,但框架在执行完成处理程序后丢弃它们似乎是明智的。这将打破保留周期并允许视图控制器被释放。
您可以通过在完成处理程序和 deinit 方法中添加额外的日志记录来确认这一点 - 我希望视图控制器在完成处理程序运行之前不会被释放。
这行代码
URLSession*
强烈捕获
let task = URLSession.shared.downloadTask(with: url) { _, _, _ in ... }
,导致内存泄漏。
避免这种情况的一种方法是将捕获更改为self
,如下所示:
weak
或者,尝试将
let task = URLSession.shared.downloadTask(with: url) { [weak self] _, _, _ in
guard let self else { return }
DispatchQueue.main.async {
print(self.view.description)
}
}
添加到 ViewController 的
self.vcTask = nil
方法中以手动打破循环。