在这个使用 URLSession 的简单案例中是否创建了一个强引用循环?

问题描述 投票:0回答:2

我对如何创建强引用以及何时发生引用循环感到有点困惑。这是一个简单的例子:

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
    持有对模型类实例的强引用
  2. URLSession 拥有对其数据任务的强引用,它拥有对闭包的强引用。
  3. 闭包对函数进行了转义,因为需要更新自身,所以持有Model实例的强引用
  4. 但是,模型实例不持有对数据任务的强引用,因此不存在引用循环。

这是正确的吗?如果是这样,我真的不明白第 4 步。为什么 Model 实例不持有对数据任务的强引用,因为任务是在 Model 类的函数中创建的?

注意:我看过几个相关的问题,但我仍然不明白为什么模型实例不持有对会话、任务或闭包的强引用。

swift automatic-ref-counting urlsession strong-reference-cycle
2个回答
0
投票

这里没有循环(正如你所注意到的)。但是,永远不会消失的

URLSession.shared
确实包含对任务的引用,该任务包含对
Model
的引用。这意味着
Model
在任务完成之前无法解除分配。 (如果
Model
具有
urlSession
属性,那么从技术上讲会有一个循环,但它不会在实践中改变任何东西。“循环”并不神奇。如果永远存在的东西持有对某物的引用,它将让那个物体永远活着。)

这通常是一件好事。 URLSession 任务在完成时会自动释放其完成块,因此

Model
仅在任务完成之前保持活动状态。只要
Model
不假设
ViewController
仍然存在(它不应该存在),就参考周期而言,这里没有任何问题。

这段代码有一点不好的地方是

Model
不会保留任务,因此它可以取消它,甚至可以检测到正在进行的任务(以避免并行发出重复请求)。对于简单的应用程序来说,这不是主要问题,但对于改进更复杂的应用程序来说是有用的。


0
投票

在您的示例中,引用循环可以发生在 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
将在闭包的生命周期内存在是很重要的。

© www.soinside.com 2019 - 2024. All rights reserved.