我试图更好地理解保留周期,特别是相对于Dispatch Queues。我正在使用AVFoundation并在sessionQueue上管理AVCaptureSession:
private let sessionQueue = DispatchQueue(label: "com.andrewferrarone.sessionQueue")
在苹果文档中的很多代码示例中我看到了:
self.sessionQueue.async { [unowned self]
//
}
这里有[unowned self]
个自我吗? self(viewController)引用self.sessionQueue
,调度到self.sessionQueue
的闭包捕获self。这是一个参考周期吗? self不引用闭包,只是DispatchQueue。如果[unowned self]
是必要的,那么根据我的理解,如果我确定自我不会为零,我只想使用unowned self
。那么我可以说我在sessionQueue
上放了一个需要很长时间的任务,并且viewController被弹出并在任务完成之前被解除分配? sessionQueue
和任务会发生什么?如果它仍然在那时,当它试图访问自己时,应用程序将崩溃。另一方面,由于无主的自我不会增加self的保留计数,因此它不会阻止viewController被释放。
所以我的问题是当一个viewController被解除分配时DispatchQueues会发生什么,以及在这种情况下会发生什么,如果在dispatchQueue任务完成之前取消分配了一个viewController?如果有人能够对这里发生的事情有所了解,那将非常有帮助和赞赏。
感谢帮助我的朋友们!
答案 0 :(得分:15)
这里有
[unowned self]
个自我吗?
不仅没有必要使用[unowned self]
,而且在异步调度的块中它非常危险。你最终得到一个指向解除分配对象的悬空指针。
如果您不想在异步调用中保持对self
的强引用,请改用[weak self]
。如果您知道在unowned
被取消分配后永远不能调用块,则应该只使用self
。显然,使用异步调用时,您不知道这一点,因此不应在该上下文中使用[unowned self]
。
您是否使用[weak self]
或使用强引用是一个问题,您是否需要异步执行的块来保持对相关对象的强引用。例如,如果您只更新视图控制器的视图控件,那么[weak self]
就可以了(更新已被解除的视图没有意义)。
对weak
和unowned
引用的更为关键的用法是避免强引用周期。但这并不适用于您提供的示例。如果视图控制器保留对块本身的一些引用(例如,您有一些闭包属性),并且那些闭包引用self
,但没有weak
/ {{1},您只需要担心这些周期。 } qualifier。
我的问题是当视图控制器被解除分配时
unowned
会发生什么?
这些队列将继续存在,任何调度的块也将继续存在,直到(a)所有调度的块完成; (b)没有更强烈的队列引用。
因此,如果异步调度具有DispatchQueue
weak
引用的块(即视图控制器),它们将在视图控制器释放后继续运行。这就是为什么在这种情况下不使用self
至关重要的原因。
对于它的价值,经验测试可能是有启发性的。考虑:
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")
}
}
,视图控制器仅在当前调度块完成之前保留,然后视图控制器将被释放,其余块将快速启动,但由于{{ 1}},[weak self]
在视图控制器关闭后不会运行。
如果您将[weak self]
替换为performSomeTask
(并明显删除weak
中的unowned
),如果您关闭视图控制器,您会看到它崩溃在排队的块有机会开始之前。这说明了?
对异步代码如此危险的原因。
如果您只是简单地删除self?.performSomeTask(...)
并让它使用对[unowned self]
的隐式强引用,您将看到在所有排队的块完成之前它不会释放视图控制器。 / p>