处理URLSession时是否总是需要[Weak self]?
Is [Weak self] always needed when dealing with 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()
}
}
Service.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 时,是否有关于如何检查它是否通常需要的规则?或者这无关紧要(由我们决定)?
您的代码的问题不在于使用 URLSession,而在于您在视图控制器中保留了一个函数:
class ViewModel {
var didLoadData: ((User) -> Void)?
}
如果 didLoadData
函数隐式或显式提及 self
(即 ViewModel 实例),除非您说 weak self
或 unowned self
.
在您给出的示例中,[weak self]
可能是不必要的。如果 ViewModel
在请求完成之前被释放,这取决于您希望发生什么。
如 URLSessionDataTask
文档中所述(强调我的):
After you create a task, you start it by calling its resume() method. The session then maintains a strong reference to the task until the request finishes or fails; you don’t need to maintain a reference to the task unless it’s useful for your app’s internal bookkeeping.
会话对任务有很强的引用。该任务对闭包有很强的引用。闭包对 ViewModel
有强引用。只要 ViewModel
没有对任务的强引用(它在您提供的代码中没有),就没有循环。
问题是您是否要确保 ViewModel
继续存在足够长的时间以执行闭包。如果您这样做(或不关心),那么您可以使用简单的强引用。如果你想阻止任务保持 ViewModel
存活,那么你应该使用弱引用。
这就是您需要考虑引用循环的方式。没有一般规则 "use weak
here." 当你想要的时候,你使用 weak
;当您不希望此闭包在释放之前一直保持 self
时。如果它创建了一个循环,则尤其如此。但是 "does this create a cycle." 没有通用的答案,这取决于哪些部分包含引用。
这也指出了您当前的 API 设计不尽如人意的地方。您在 init
中超过了 didLoadData
。这很可能会创建引用循环并强制您的调用者使用 weak
。相反,如果您在 loadUser()
上创建 didLoadDdata
完成处理程序,那么您可以避免该问题并使调用者的生活更轻松。
func loadUser(completion: @escaping ((User?) -> Void)? = nil) {
service.fetchUser {
self?.user = user
didLoadData?(user)
}
}
(你当前的 API 在任何情况下都有竞争条件。你在 didLoadData
可以设置之前开始 loadUser()
。你可能假设完成处理程序不会'在你设置 dataDidLoad
之前不会完成,但没有真正的承诺。这可能是真的,但充其量是脆弱的。)
我不知道在这种情况下是否需要使用 [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()
}
}
Service.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 时,是否有关于如何检查它是否通常需要的规则?或者这无关紧要(由我们决定)?
您的代码的问题不在于使用 URLSession,而在于您在视图控制器中保留了一个函数:
class ViewModel {
var didLoadData: ((User) -> Void)?
}
如果 didLoadData
函数隐式或显式提及 self
(即 ViewModel 实例),除非您说 weak self
或 unowned self
.
在您给出的示例中,[weak self]
可能是不必要的。如果 ViewModel
在请求完成之前被释放,这取决于您希望发生什么。
如 URLSessionDataTask
文档中所述(强调我的):
After you create a task, you start it by calling its resume() method. The session then maintains a strong reference to the task until the request finishes or fails; you don’t need to maintain a reference to the task unless it’s useful for your app’s internal bookkeeping.
会话对任务有很强的引用。该任务对闭包有很强的引用。闭包对 ViewModel
有强引用。只要 ViewModel
没有对任务的强引用(它在您提供的代码中没有),就没有循环。
问题是您是否要确保 ViewModel
继续存在足够长的时间以执行闭包。如果您这样做(或不关心),那么您可以使用简单的强引用。如果你想阻止任务保持 ViewModel
存活,那么你应该使用弱引用。
这就是您需要考虑引用循环的方式。没有一般规则 "use weak
here." 当你想要的时候,你使用 weak
;当您不希望此闭包在释放之前一直保持 self
时。如果它创建了一个循环,则尤其如此。但是 "does this create a cycle." 没有通用的答案,这取决于哪些部分包含引用。
这也指出了您当前的 API 设计不尽如人意的地方。您在 init
中超过了 didLoadData
。这很可能会创建引用循环并强制您的调用者使用 weak
。相反,如果您在 loadUser()
上创建 didLoadDdata
完成处理程序,那么您可以避免该问题并使调用者的生活更轻松。
func loadUser(completion: @escaping ((User?) -> Void)? = nil) {
service.fetchUser {
self?.user = user
didLoadData?(user)
}
}
(你当前的 API 在任何情况下都有竞争条件。你在 didLoadData
可以设置之前开始 loadUser()
。你可能假设完成处理程序不会'在你设置 dataDidLoad
之前不会完成,但没有真正的承诺。这可能是真的,但充其量是脆弱的。)