在处理URLSession时,是否总是需要【弱自我】



我不知道在这种情况下是否需要使用[weak self]

HTTPClient.swift:

struct HTTPClient {
let session = URLSession.shared
func get(url: URL, completion: @escaping (Data) -> Void) {
session.dataTask(with: url) { data, urlResponse, error in
completion(data) // assume everything will go well
}.resume()
}
}

服务.swift

struct Service {
let httpClient: HTTPClient
init(httpClient: HTTPClient = HTTPClient()) {
self.httpClient = httpClient
}
func fetchUser(completion: @escaping (User) -> Void) {
httpClient.get("urlToGetUser") { data in
// transform data to User 
completion(user)
} 
}
}

ViewModel.swift

class ViewModel {
let service: Service
let user: User?
var didLoadData: ((User) -> Void)?
init(service: Service) {
self.service = service
loadUser()
}
func loadUser() {
service.fetchUser { [weak self] user in // is  [weak self] really needed ?
self?.user = user
self?.didLoadData?(user)
}
}
}

这里真的需要它来使用[weak self]吗?当我们处理一个我们不知道闭包发生了什么的API时,是否有一条关于如何检查是否需要的规则?还是这没关系(由我们决定(?

在您给出的示例中,[weak self]可能是不必要的。这取决于如果在请求完成之前释放ViewModel,您希望发生什么。

URLSessionDataTask文档中所述(强调矿(:

创建任务后,通过调用其resume((方法来启动它。然后,会话保持对任务的强引用,直到请求完成或失败;你不需要维护对任务的引用,除非它对你的应用程序的内部记账有用。

会话具有对任务的强引用。该任务强烈引用了闭包。该闭包强烈引用了ViewModel。只要ViewModel没有对任务的强引用(在您提供的代码中没有(,那么就没有循环。

问题是是否要确保ViewModel继续存在足够长的时间,以便执行闭包。如果你在乎(或者不在乎(,那么你可以使用一个简单的强引用。如果要阻止任务保持ViewModel的活动状态,则应使用弱引用。

这就是你需要思考参考周期的方式。这里没有"使用weak"的一般规则。当这就是你的意思时,你使用weak;当您不希望此闭包在self发布之前一直存在时。如果它创造了一个循环,这一点尤其正确。但对于"这会创造一个循环吗"并没有一个普遍的答案。这取决于哪些作品有参考文献。

这也指出了您当前的API设计没有达到预期效果的地方。您正在通过init中的didLoadData。这很可能会创建引用循环,并强制调用方使用weak。如果您将didLoadDdata作为loadUser()上的完成处理程序,则可以避免该问题,并使调用者的生活更轻松。

func loadUser(completion: @escaping ((User?) -> Void)? = nil) {
service.fetchUser {
self?.user = user
didLoadData?(user)
}
}

(在任何情况下,您当前的API都有竞争条件。在设置didLoadData之前,您将启动loadUser()。您可能会假设完成处理程序在您设置dataDidLoad之前不会完成,但这并没有真正的承诺。这可能是真的,但它充其量是脆弱的。(

代码的问题不是URLSession的使用,而是在视图控制器中保留了一个函数:

class ViewModel {
var didLoadData: ((User) -> Void)?
}

如果didLoadData函数隐式或显式地提到self(即ViewModel实例(,则除非说weak selfunowned self,否则会出现保留循环和内存泄漏。

最新更新