gpt4 book ai didi

ios - 如何通过在闭包中使用 self 来防止内存泄漏

转载 作者:可可西里 更新时间:2023-11-01 00:54:16 25 4
gpt4 key购买 nike

我有下载文件的类(class):

class FileDownloader {

private let downloadsSession = URLSession(configuration: .default)
private var task: URLSessionDownloadTask?
private let url: URL

init(url: URL) {
self.url = url
}

public func startDownload(){
download()
}

private func download(){

task = downloadsSession.downloadTask(with: url) {[weak self] (location, response, error) in
guard let weakSelf = self else {
assertionFailure("self was deallocated")
return }
weakSelf.saveDownload(sourceUrl: weakSelf.url, location: location, response: response, error: error)
}

task!.resume()
}

private func saveDownload(sourceUrl : URL, location : URL?, response : URLResponse?, error : Error?) {
if error != nil {
assertionFailure("error \(String(describing: error?.localizedDescription))")
return }

let destinationURL = localFilePath(for: sourceUrl)

let fileManager = FileManager.default
try? fileManager.removeItem(at: destinationURL)
do {
try fileManager.copyItem(at: location!, to: destinationURL)
print("save was completed at \(destinationURL) from \(String(describing: location))")
} catch let error {
print("Could not copy file to disk: \(error.localizedDescription)")
}
}

private func localFilePath(for url: URL) -> URL {
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
return documentsPath.appendingPathComponent(url.lastPathComponent)
}
}

当我调用 startDownload() 时,我在调试时遇到错误:

assertionFailure("self was deallocated")

当我将下载功能更改为:

private func download(){

task = downloadsSession.downloadTask(with: url) {(location, response, error) in
self.saveDownload(sourceUrl: self.url, location: location, response: response, error: error)
}

task!.resume()
}

一切正常,但我担心它可能会导致未在内存中正确释放的对象出现问题。如何避免这种情况?我做的对吗?

最佳答案

首先,为什么会出现断言失败?因为您让 FileDownloader 实例超出范围。你没有分享你是如何调用它的,但你很可能将它用作局部变量。如果你解决了这个问题,你的问题就会消失。

其次,当您更改实现以删除 [weak self] 模式时,您没有强引用循环,而是您只是指示它不要释放 FileDownloader 直到下载完成。如果那是您想要的行为,那很好。这是一个完全可以接受的模式,“让它在异步任务完成之前保持对自身的引用”。事实上,这正是 URLSessionTask 所做的。显然,您需要完全清楚省略 [weak self] 模式的含义,因为在某些情况下它会引入强引用循环,但在这种情况下不会。


强引用循环仅在您有两个对象彼此具有持久性强引用时发生(或者有时可能涉及两个以上的对象)。对于 URLSession,当下载完成时,Apple 谨慎地编写了 downloadTask 方法,以便在调用它后显式释放闭包,解决任何潜在的强引用循环。

例如,考虑这个例子:

class Foo {
func performAfterFiveSeconds(block: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
self.doSomething()

block()
}
}

func doSomething() { ... }
}

上面的代码很好,因为 asyncAfter 在运行时释放了闭包。但是考虑这个例子,我们将闭包保存在我们自己的 ivar 中:

class BarBad {
private var handler: (() -> Void)?

func performAfterFiveSeconds(block: @escaping () -> Void) {
handler = block

DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
self.calledWhenDone()
}
}

func calledWhenDone() {
// do some stuff

doSomething()

// when done, call handler

handler?()
}

func doSomething() { ... }
}

现在这是一个潜在的问题,因为这次我们将闭包保存在一个 ivar 中,创建了对闭包的强引用,并引入了经典强引用循环的风险。

但幸运的是,这很容易补救:

class BarGood {
private var handler: (() -> Void)?

func performAfterFiveSeconds(block: @escaping () -> Void) {
handler = block

DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
self.calledWhenDone()
}
}

func calledWhenDone() {
// do some stuff

doSomething()

// when done, call handler

handler?()

// make sure to release handler when done with it to prevent strong reference cycle

handler = nil
}

func doSomething() { ... }
}

这解决了将 handler 设置为 nil 时的强引用循环。这实际上就是 URLSession(以及 GCD 方法,如 asyncasyncAfter)所做的。他们保存闭包直到调用它,然后释放它。

关于ios - 如何通过在闭包中使用 self 来防止内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54430997/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com