我对如何创建强引用以及何时发生引用循环有点困惑。这里有一个简单的例子:
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)
}
}
这是我对上面代码中引用如何工作的理解:
1.变量model
保存对Model类示例的强引用
- URLSession持有对其数据任务的强引用,而数据任务持有对其闭包的强引用。
1.闭包转义函数,因为它需要更新self,所以它持有对Model示例的强引用
1.但是,Model示例不持有对数据任务的强引用,因此没有引用循环。
这是正确的吗?如果是这样,我真的不明白步骤4。为什么Model示例不持有对数据任务的强引用,因为任务是在Model类的函数中创建的?
注意:我已经看到了几个相关的问题,但我仍然不明白为什么Model示例不保存对会话、任务或闭包的强引用。
2条答案
按热度按时间50few1ms1#
这里没有循环但是,
URLSession.shared
永远不会消失,它确实保存了对任务的引用,而任务保存了对Model
的引用。这意味着Model
在任务完成之前无法释放。(如果Model
具有urlSession
属性,那么从技术上讲,将存在一个循环,但实际上它不会改变任何东西。“循环”并不神奇。如果一个永生的东西持有对某个东西的引用,它将使该对象永远活着。)这通常是一件好事。URLSession任务在完成时会自动释放其完成块,因此
Model
只保持活动状态,直到任务完成。只要Model
不假设ViewController
仍然存在(它不应该),就引用周期而言,这里没有什么问题。这段代码有一点不好,那就是
Model
没有保留任务,所以它可以取消它,甚至检测到一个任务正在进行中(以避免并行发出重复请求)。对于简单的应用程序来说,这不是一个大问题,但对于更复杂的应用程序来说,这是一个有用的改进。cedebl8k2#
让我们一个一个来回答你的问题:
这是我对上面代码中引用如何工作的理解:
1.变量
model
保存对Model
类示例的强引用。正确,视图控制器将保留该强引用,直到视图控制器本身被释放。
URLSession
持有对其数据任务的强引用,该数据任务持有对其闭包的强引用。URLSession
会保持这个强引用,直到请求完成/失败,此时数据任务被释放。您不需要保留对数据任务的引用,因为URLSession
会在请求期间自动挂起它。话虽如此,我们通常会保留自己的weak
对它的引用,如果/当我们可能想要取消请求时,我们将使用它。(见下文。)1.闭包转义函数,因为它需要更新self,所以它持有对Model示例的强引用
是的,就目前而言,闭包维护了对
self
的强引用,并且在数据任务完成或失败之前,这个闭包不会被释放。顺便说一句,我们通常不会这样做。通常我们会在这个闭包中使用
[weak self]
捕获列表,这样它就不会保持对self
的强引用。例如,你可以:1.但是,
Model
示例不持有对数据任务的强引用,因此没有引用周期。是的。或者更准确地说,正如问题中所实现的,
URLSession
将保持对self
的强引用,当请求完成或失败时,将释放该强引用。但是,同样,如果我们如上所述使用[weak self]
捕获列表,它根本不会保持强引用,并且一旦视图控制器被解除分配,X1 M13 N1 X将被解除分配。更好的是,除非我们明确需要任务在
Model
由于某种原因被释放的情况下继续运行,否则我们将在Model
被释放时cancel
task
:注意,我们既不需要也不想保持对
URLSessionTask
的强引用。(URLSession
将管理URLSessionTask
的生命周期。)但是我们保留了自己的weak
引用,当URLSessionTask
完成时,它将自动设置为nil
。这样,如果在Model
释放时请求尚未完成,我们可以取消请求。但是如果请求已经完成,task
引用将自动为我们设置为nil
,在这种情况下,task?.cancel()
将成为“no op”。