如何使用dispatchQueues创建一个引用循环?
如何使用dispatchQueues创建一个引用循环?
我觉得我一直对于创建引用循环的时机存在误解。以前我认为几乎在任何你有一个块且编译器强制你写.self
的地方,这就是我创建引用循环的迹象,需要使用[weak self] in
。
但是下面的设置并不会创建引用循环。
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution
class UsingQueue {
var property : Int = 5
var queue : DispatchQueue? = DispatchQueue(label: "myQueue")
func enqueue3() {
print("入队")
queue?.asyncAfter(deadline: .now() + 3) {
print(self.property)
}
}
deinit {
print("UsingQueue 被释放")
}
}
var u : UsingQueue? = UsingQueue()
u?.enqueue3()
u = nil
这个块只会在3秒钟内保留self
,然后释放它。如果我使用async
而不是asyncAfter
,那么几乎是立即释放的。
据我所了解,这里的设置是:
self ---> queue self <--- block
队列只是块的外壳/封装。这就是为什么即使我将队列设为nil
,块也会继续执行。它们是独立的。
那么有没有仅使用队列就会创建引用循环的设置呢?
据我所了解,[weak self]
只应该用于除了引用循环之外的其他原因,比如控制块的流程。例如:
您想要保留对象并运行您的块,然后释放它吗?一个真实的情况是即使视图已从屏幕上移除,也要完成此事务...
或者您希望使用[weak self] in
,以便在对象被释放时可提前退出。例如,一些纯粹的 UI 操作,如不再需要停止加载指示器。
就我所知,如果我使用闭包,情况就会不同,例如:
import PlaygroundSupport
import Foundation
PlaygroundPage.current.needsIndefiniteExecution
class UsingClosure {
var property : Int = 5
var closure : (() -> Void)?
func closing() {
closure = {
print(self.property)
}
}
func execute() {
closure!()
}
func release() {
closure = nil
}
deinit {
print("UsingClosure 被释放")
}
}
var cc : UsingClosure? = UsingClosure()
cc?.closing()
cc?.execute()
cc?.release() // 要么调用此方法,要么在闭包中使用[weak self],否则会有一个引用循环
cc = nil
在闭包示例中,设置更像是:
self ----> block self <--- block
因此它是一个引用循环,除非我将 block 设置为捕获nil
,否则不会释放。
编辑:
class C {
var item: DispatchWorkItem!
var name: String = "Alpha"
func assignItem() {
item = DispatchWorkItem { // Oops!
print(self.name)
}
}
func execute() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: item)
}
deinit {
print("deinit hit!")
}
}
通过上述代码,我成功创建了一个泄漏,也就是在 Xcode 的内存图中我看到一个循环,而不是一条直线。我看到了紫色的指示器。我认为这个设置非常类似于存储的闭包创建泄漏的方式。这与您的两个示例不同,其中执行永远不会完成。在这个示例中,执行已完成,但由于引用,它仍然存在于内存中。
我认为引用关系如下:
┌─────────┐─────────────self.item──────────────┌────────┐
│ self │ │workItem│
└─────────┘────item = DispatchWorkItem {...}───└────────┘
如何使用dispatchQueues创建引用循环?
在上述内容中,提到了使用dispatchQueues创建引用循环的原因以及解决方法。创建引用循环的原因是因为GCD会保持对所有已排队任务的dispatch queues的引用。而解决引用循环的方法则是使用[weak self]来避免强引用。
首先,提到了一种常见的引用循环情况,即在重复定时器中使用了未标记为[weak self]的block。即使视图控制器被解除引用,GCD仍然会继续执行定时器。为了解决这个问题,可以在block中使用[weak self]来避免对Ticker对象的持久强引用。
其次,还提到了另一种情况,即在长时间或无限长度的dispatched task中使用了未标记为[weak self]的block。这种情况下,即使调用了deinit方法,Calculator对象仍然无法释放。为了解决这个问题,可以在block中使用[weak self]来避免对Calculator对象的持久强引用。
此外,还强调了在大多数GCD使用场景中,选择使用[weak self]并不是为了解决引用循环的问题,而是根据实际需求决定是否需要在任务完成后保持对self的强引用。
最后,提到了使用Xcode的“Debug Memory Graph”工具来分析强引用、识别循环等。同时还有关于Xcode 12/13版本的问题,建议在Stack Overflow上提问以获取更详细的解答。
使用dispatchQueues创建引用循环的原因是GCD会保持对已排队任务的引用,解决方法是使用[weak self]来避免强引用。在大多数情况下,选择使用[weak self]取决于任务完成后是否需要保持对self的强引用。使用Xcode的“Debug Memory Graph”工具可以帮助分析和解决引用循环问题。