我对如何创建强引用以及何时发生引用循环感到有点困惑。这是一个简单的例子:
class Model {
var foo: Data?
func makeRequest(url: URL) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
// assume request completes successfully
self.foo = data!
}
task.resume()
}
}
class ViewController: UIViewController {
var model = Model()
let url = URL(string: "abc.com")! // assume URL is valid
override func viewDidLoad() {
super.viewDidLoad()
model.makeRequest(url: url)
}
}
这是我对上面代码中引用如何工作的理解:
model
持有对模型类实例的强引用这是正确的吗?如果是这样,我真的不明白第 4 步。为什么 Model 实例不持有对数据任务的强引用,因为任务是在 Model 类的函数中创建的?
注意:我看过几个相关的问题,但我仍然不明白为什么模型实例不持有对会话、任务或闭包的强引用。
这里没有循环(正如你所注意到的)。但是,永远不会消失的
URLSession.shared
确实包含对任务的引用,该任务包含对Model
的引用。这意味着 Model
在任务完成之前无法解除分配。 (如果 Model
具有 urlSession
属性,那么从技术上讲会有一个循环,但它不会在实践中改变任何东西。“循环”并不神奇。如果永远存在的东西持有对某物的引用,它将让那个物体永远活着。)
这通常是一件好事。 URLSession 任务在完成时会自动释放其完成块,因此
Model
仅在任务完成之前保持活动状态。只要 Model
不假设 ViewController
仍然存在(它不应该存在),就参考周期而言,这里没有任何问题。
这段代码有一点不好的地方是
Model
不会保留任务,因此它可以取消它,甚至可以检测到正在进行的任务(以避免并行发出重复请求)。对于简单的应用程序来说,这不是主要问题,但对于改进更复杂的应用程序来说是有用的。
在您的示例中,引用循环可以发生在 Model 和传递给
URLSession.shared.dataTask(with:completionHandler:)
的闭包之间。
当调用
makeRequest(url:)
时,它会创建一个新的URLSessionDataTask
并传递给它一个捕获self
的闭包(即Model
的实例)。这个闭包将在数据任务完成时执行,并将下载的Data
分配给self.foo
。但是,如果 self
在执行闭包之前被释放,那么将下载的数据分配给 self.foo 将导致崩溃。
在这种情况下,如果
URLSessionDataTask
保留对闭包的强引用并且闭包保留对self
(即Model
的实例)的强引用,则可能会发生引用循环。由于 self
保留了对 URLSessionDataTask
的强引用(通过任务变量),这创建了一个引用循环。
要打破引用循环,可以在闭包中使用捕获列表将 self 捕获为弱引用或无主引用,而不是强引用:
class Model {
var foo: Data?
func makeRequest(url: URL) {
let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard let self = self else { return }
// assume request completes successfully
self.foo = data!
}
task.resume()
}
}
通过将 self 捕获为弱引用,您可以确保即使闭包由 URLSessionDataTask 保留,也可以释放 Model 实例。然而,由于 self 现在是一个弱引用,在闭包中使用它之前必须防止它成为 nil。
或者,如果您确定 Model 实例在闭包的生命周期内始终存在,则可以捕获 self 作为无主引用:
class Model {
var foo: Data?
func makeRequest(url: URL) {
let task = URLSession.shared.dataTask(with: url) { [unowned self] data, response, error in
// assume request completes successfully
self.foo = data!
}
task.resume()
}
}
通过捕获 self 作为 unowned 参考,您无需
guard
反对 self 为零。但是,如果 self 在执行闭包之前被释放,那么访问它会导致崩溃。因此,在使用 unowned
引用之前,确定 self将在闭包的生命周期内存在是很重要的。