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

eufgjt7s  于 2023-04-19  发布在  Swift
关注(0)|答案(2)|浏览(105)

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

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类示例的强引用

  1. URLSession持有对其数据任务的强引用,而数据任务持有对其闭包的强引用。
    1.闭包转义函数,因为它需要更新self,所以它持有对Model示例的强引用
    1.但是,Model示例不持有对数据任务的强引用,因此没有引用循环。
    这是正确的吗?如果是这样,我真的不明白步骤4。为什么Model示例不持有对数据任务的强引用,因为任务是在Model类的函数中创建的?
    注意:我已经看到了几个相关的问题,但我仍然不明白为什么Model示例不保存对会话、任务或闭包的强引用。
50few1ms

50few1ms1#

这里没有循环但是,URLSession.shared永远不会消失,它确实保存了对任务的引用,而任务保存了对Model的引用。这意味着Model在任务完成之前无法释放。(如果Model具有urlSession属性,那么从技术上讲,将存在一个循环,但实际上它不会改变任何东西。“循环”并不神奇。如果一个永生的东西持有对某个东西的引用,它将使该对象永远活着。)
这通常是一件好事。URLSession任务在完成时会自动释放其完成块,因此Model只保持活动状态,直到任务完成。只要Model不假设ViewController仍然存在(它不应该),就引用周期而言,这里没有什么问题。
这段代码有一点不好,那就是Model没有保留任务,所以它可以取消它,甚至检测到一个任务正在进行中(以避免并行发出重复请求)。对于简单的应用程序来说,这不是一个大问题,但对于更复杂的应用程序来说,这是一个有用的改进。

cedebl8k

cedebl8k2#

让我们一个一个来回答你的问题:
这是我对上面代码中引用如何工作的理解:
1.变量model保存对Model类示例的强引用。
正确,视图控制器将保留该强引用,直到视图控制器本身被释放。

  1. URLSession持有对其数据任务的强引用,该数据任务持有对其闭包的强引用。
    URLSession会保持这个强引用,直到请求完成/失败,此时数据任务被释放。您不需要保留对数据任务的引用,因为URLSession会在请求期间自动挂起它。话虽如此,我们通常会保留自己的weak对它的引用,如果/当我们可能想要取消请求时,我们将使用它。(见下文。)
    1.闭包转义函数,因为它需要更新self,所以它持有对Model示例的强引用
    是的,就目前而言,闭包维护了对self的强引用,并且在数据任务完成或失败之前,这个闭包不会被释放。
    顺便说一句,我们通常不会这样做。通常我们会在这个闭包中使用[weak self]捕获列表,这样它就不会保持对self的强引用。例如,你可以:
let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
    // see if `self` was released
    guard let self else { return }

    // see if request succeeded
    guard let data else {
        print(error ?? URLError(.badServerResponse))
        return
    }

    // if we got here, we have our `Data`; we always avoid forced unwrapping operators when dealing with data from a remote server
    self.foo = data
}

1.但是,Model示例不持有对数据任务的强引用,因此没有引用周期。
是的。或者更准确地说,正如问题中所实现的,URLSession将保持对self的强引用,当请求完成或失败时,将释放该强引用。但是,同样,如果我们如上所述使用[weak self]捕获列表,它根本不会保持强引用,并且一旦视图控制器被解除分配,X1 M13 N1 X将被解除分配。
更好的是,除非我们明确需要任务在Model由于某种原因被释放的情况下继续运行,否则我们将在Model被释放时canceltask

class Model {
    var foo: Data?
    private weak var task: URLSessionTask?

    deinit {
        task?.cancel()
    }
    
    func makeRequest(url: URL) {
        let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            guard let self else { return }
        
            guard let data else {
                print(error ?? URLError(.badServerResponse))
                return
            }
        
            self.foo = data
        }

        task.resume()
        self.task = task
    }
}

注意,我们既不需要也不想保持对URLSessionTask的强引用。(URLSession将管理URLSessionTask的生命周期。)但是我们保留了自己的weak引用,当URLSessionTask完成时,它将自动设置为nil。这样,如果在Model释放时请求尚未完成,我们可以取消请求。但是如果请求已经完成,task引用将自动为我们设置为nil,在这种情况下,task?.cancel()将成为“no op”。

相关问题