我觉得在创建参考循环时,我总是误解了这一点。在我习惯认为几乎所有您拥有块的地方以及编译器迫使您编写.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("enqueued")
queue?.asyncAfter(deadline: .now() + 3) {
print(self.property)
}
}
deinit {
print("UsingQueue deinited")
}
}
var u : UsingQueue? = UsingQueue()
u?.enqueue3()
u = nil
该区块仅保留self
3秒钟。然后释放它。如果我使用async
而不是asyncAfter
,那么它几乎是即时的。
据我了解,这里的设置是:
self ---> queue
self <--- block
队列只是该块的外壳/包装器。这就是为什么即使我nil
队列,该块也将继续执行的原因。他们是独立的。
那么有没有仅使用队列并创建参考周期的设置?
据我了解,[weak self]
仅用于参考循环以外的其他原因,即控制块的流量。例如
您是否要保留对象并运行您的块然后释放它?真正的情况是即使视图已从屏幕中删除,也要完成此交易...
或者您想使用[weak self] in
,以便在对象被释放后可以提前退出。例如不再需要一些纯粹的UI,例如停止加载微调器
FWIW我了解,如果我使用闭包,那么情况会有所不同,即如果我这样做:
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 deinited")
}
}
var cc : UsingClosure? = UsingClosure()
cc?.closing()
cc?.execute()
cc?.release() // Either this needs to be called or I need to use [weak self] for the closure otherwise there is a reference cycle
cc = nil
在闭包示例中,设置类似于:
self ----> block
self <--- block
因此,这是一个参考周期,除非我将捕获的块设置为nil
,否则它不会取消分配。
答案 0 :(得分:2)
您说:
据我了解,这里的设置是:
self ---> queue self <--- block
队列只是该块的外壳/包装器。这就是为什么即使我
nil
队列,该块也将继续执行的原因。他们是独立的。
self
恰好具有对队列的强引用这一事实是无关紧要的。一种更好的思考方式是,GCD本身保留对所有排队的调度队列的引用。 (这类似于自定义URLSession
实例,该实例在完成该会话上的所有任务之前不会被释放。)
因此,GCD保留对已调度任务的队列的引用。队列对分配的块/项目保持强烈的引用。排队的块对它们捕获的任何引用类型均保持强烈引用。分派的任务完成后,它将解析对任何捕获的引用类型的任何强引用,并将其从队列中删除(除非您将自己的引用保留在其他位置。),通常可以解决任何强引用周期。
将其放在一旁,因为缺少[weak self]
会给您带来麻烦,GCD出于某种原因(例如调度源)保留了对该块的引用。经典示例是重复计时器:
class Ticker {
private var timer: DispatchSourceTimer?
func startTicker() {
let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".ticker")
timer = DispatchSource.makeTimerSource(queue: queue)
timer!.schedule(deadline: .now(), repeating: 1)
timer!.setEventHandler { // whoops; missing `[weak self]`
self.tick()
}
timer!.resume()
}
func tick() { ... }
}
即使关闭了我启动上述计时器的视图控制器,GCD也会一直触发该计时器,并且Ticker
不会被释放。如“调试内存图”功能所示,在startTicker
例程中创建的块将保持对Ticker
对象的持久性强引用:
如果我在该块中使用[weak self]
作为该调度队列中计划的计时器的事件处理程序,显然可以解决此问题。
其他情况包括缓慢(或不确定长度)的已调度任务,您要在其中cancel
(例如,在deinit
中)执行任务:
class Calculator {
private var item: DispatchWorkItem!
deinit {
item?.cancel()
item = nil
}
func startCalculation() {
let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".calcs")
item = DispatchWorkItem { // whoops; missing `[weak self]`
while true {
if self.item?.isCancelled ?? true { break }
self.calculateNextDataPoint()
}
self.item = nil
}
queue.async(execute: item)
}
func calculateNextDataPoint() {
// some intense calculation here
}
}
所有这些,在绝大多数GCD用例中,选择[weak self]
并不是强参考周期之一,而仅仅是我们是否介意是否强参考{{1} }一直持续到完成任务为止。
如果我们要在完成任务后更新UI,则无需关闭视图控制器及其视图,只要视图控制器被关闭,它就可以在UI中保持一些UI更新。
如果在任务完成后需要更新数据存储,那么如果我们想确保更新发生,我们绝对不希望使用self
。
通常,已分派的任务不够重要,无法担心[weak self]
的寿命。例如,完成请求后,您可能需要self
完成处理程序将UI更新分派回主队列。当然,从理论上讲,我们希望使用URLSession
(因为没有理由保持已被关闭的视图控制器的视图层次结构不变),但是这又给我们的代码增加了噪音,通常没有什么实质性的好处。
无关,但是操场是测试记忆行为的可怕地方,因为它们有自己的特质。最好在实际的应用中进行操作。另外,在实际的应用程序中,您可以使用“调试内存图”功能,在其中可以查看实际的强引用。参见https://stackoverflow.com/a/30993476/1271826。