当 UIViewController 被释放时调度队列会发生什么?

What happens to Dispatch Queues when UIViewController is Deallocated?

我试图更好地理解保留周期,尤其是与调度队列相关的。我正在使用 AVFoundation 并在 sessionQueue 上管理 AVCaptureSession:

private let sessionQueue = DispatchQueue(label: "com.andrewferrarone.sessionQueue")

在苹果文档的很多代码示例中,我看到了这个:

self.sessionQueue.async { [unowned self]
    //
}

这里的[unowned self] self 有必要吗? self(viewController)引用 self.sessionQueue 并且分派给 self.sessionQueue 的闭包捕获 self。这是参考循环吗? self 没有引用闭包,只是 DispatchQueue。如果 [unowned self] 是必要的,那么据我了解,如果我确定 self 不会为零,我只想使用 unowned self 。那么假设我在 sessionQueue 上放置了一个任务,这需要很长时间,并且 viewController 在任务完成之前弹出并被释放? sessionQueue 和任务会怎样?如果它仍然存在,当它尝试访问自身时,应用程序将崩溃。另一方面,由于 unowned self 不会增加 self 的保留计数,因此它不会阻止 viewController 被释放。

所以我的问题是当 viewController 被释放时 DispatchQueues 会发生什么?在这种情况下,如果 viewController 在 dispatchQueue 任务完成之前被释放会发生什么?如果有人能阐明这里发生的一切,那将非常有帮助和感激。

感谢朋友们的帮助!

Is the [unowned self] self here necessary?

不仅没有必要使用 [unowned self],而且在异步调度块中它非常危险。您最终得到一个指向已释放对象的悬空指针。

如果您不想在异步调用中保留对 self 的强引用,请改用 [weak self]。如果您知道在 self 被释放后永远无法调用块,您应该只使用 unowned。显然,对于异步调用,您不知道这一点,因此不应在该上下文中使用 [unowned self]

使用[weak self]还是使用强引用是一个问题,就是你是否需要异步执行的块来保持对相关对象的强引用。例如,如果您只更新视图控制器的视图控件,那么 [weak self] 就可以了(没有必要更新已关闭的视图)。

weakunowned引用更关键的用途是避免强引用循环。但这不适用于您提供的示例。如果视图控制器保留对块本身的一些引用(例如,你有一些闭包 属性)并且那些闭包引用 self,但没有 weak/,你只需要担心这些循环unowned 预选赛。

My question is what happens to DispatchQueues when a view controller is deallocated?

这些队列将继续存在,任何已分派的块也将继续存在,直到 (a) 所有已分派的块完成; (b) 不再有对队列的强引用。

因此,如果您异步分派带有 weakself(即视图控制器)的引用的块,它们将在视图控制器被释放后继续 运行。这就是为什么在这种情况下不使用 unowned 很重要。


就其价值而言,实证检验可能具有启发性。考虑:

class SecondViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let queue = DispatchQueue(label: "com.domain.app.SecondViewController")

        for i in 0 ..< 10 {
            queue.async { [weak self] in
                print("closure \(i) start")
                self?.performSomeTask(i)
                print("closure \(i) finish")
            }
        }
    }

    private func performSomeTask(_ value: Int) {
        print("performSomeTask starting \(value)")
        Thread.sleep(forTimeInterval: 5)        // you wouldn't generally `sleep`, but merely for diagnostic purposes
        print("performSomeTask finishing \(value)")
    }

    deinit {
        print("deinit SecondViewController")
    }

}

如果在分派的块排队和 运行ning 时关闭此视图控制器,您将看到:

  • 使用[weak self],视图控制器只保留到当前调度块完成,然后视图控制器将被释放,其余块将快速触发,但是由于 [weak self]performSomeTask 不会在视图控制器关闭后 运行。

  • 如果将 weak 替换为 unowned(并且显然删除了 self?.performSomeTask(...) 中的 ?),您会看到它崩溃在排队的块有机会启动之前关闭视图控制器。这说明了为什么 [unowned self] 对于异步代码如此危险。

  • 如果你简单地完全删除 [weak self] 并让它使用对 self 的隐式强引用,你会看到它不会释放视图控制器,直到所有排队块完成。