如何使用dispatchQueues创建参考周期?

时间:2019-05-09 13:57:18

标签: swift memory-management memory-leaks closures grand-central-dispatch

我觉得在创建参考循环时,我总是误解了这一点。在我习惯认为几乎所有您拥有块的地方以及编译器迫使您编写.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,否则它不会取消分配。

1 个答案:

答案 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对象的持久性强引用:

repeating timer memory graph

如果我在该块中使用[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
    }
}

dispatch work item memory graph

所有这些,在绝大多数GCD用例中,选择[weak self]并不是强参考周期之一,而仅仅是我们是否介意是否强参考{{1} }一直持续到完成任务为止。

  • 如果我们要在完成任务后更新UI,则无需关闭视图控制器及其视图,只要视图控制器被关闭,它就可以在UI中保持一些UI更新。

  • 如果在任务完成后需要更新数据存储,那么如果我们想确保更新发生,我们绝对不希望使用self

  • 通常,已分派的任务不够重要,无法担心[weak self]的寿命。例如,完成请求后,您可能需要self完成处理程序将UI更新分派回主队列。当然,从理论上讲,我们希望使用URLSession(因为没有理由保持已被关闭的视图控制器的视图层次结构不变),但是这又给我们的代码增加了噪音,通常没有什么实质性的好处。


无关,但是操场是测试记忆行为的可怕地方,因为它们有自己的特质。最好在实际的应用中进行操作。另外,在实际的应用程序中,您可以使用“调试内存图”功能,在其中可以查看实际的强引用。参见https://stackoverflow.com/a/30993476/1271826