毫无疑问,当控制器消失时,您是否必须使 CADisplayLink 失效()?

Definitively, do you have to invalidate() a CADisplayLink when the controller disappears?

假设你每天都有 CADisplayLink

class Test: UIViewController {

    private var _ca : CADisplayLink?

    @IBAction func frames() {

        _ca?.invalidate()
        _ca = nil

        _ca = CADisplayLink(
            target: self,
            selector: #selector(_step))
        _ca?.add(to: .main, forMode: .commonModes)
    }

    @objc func _step() {

        let s = Date().timeIntervalSince1970
        someAnime.seconds = CGFloat(s)
    }

最终视图控制器被关闭。

有没有人真正明确地知道,

当视图控制器被关闭时,您是否必须显式调用 .invalidate()(实际上是 nil _ca)?

(所以也许在 deinit 或 viewWillDisappear 中,或任何你喜欢的地方。)

文档毫无价值,而且我不够聪明,无法查看源代码。我从来没有找到任何人真正、明确地知道这个问题的答案。

是否必须显式无效,如果 VC 消失,是否会保留并保留 运行?

invalidate()的方法定义:

Removing the display link from all run loop modes causes it to be released by the run loop. The display link also releases the target.

对我来说,这意味着 displaylink 持有目标,运行 loop 持有 DispayLink。

此外,根据 this link 我发现,调用 invalidate() 来清理 CADisplayLink.

看起来相当重要

我们实际上可以使用 XCode 的精彩 Memory graph debugger:

来验证

我创建了一个测试项目,其中 DetailViewController 被推送到导航堆栈。

class DetailViewController: UIViewController {

    private var displayLink : CADisplayLink?

    override func viewDidAppear() {
        super.viewDidAppear()

        startDisplayLink()
    }

    func startDisplayLink() {
        startTime = CACurrentMediaTime()

        displayLink = CADisplayLink(target: self, 
                                  selector: #selector(displayLinkDidFire))

        displayLink?.add(to: .main, forMode: .commonModes)
    }
}

这会在视图出现时启动 CADispalyLink

如果我们检查内存图,我们可以看到 DetailViewController 仍在内存中并且 CADisplayLink 保留其引用。此外,DetailViewController 包含对 CADisplayLink.

的引用

如果我们现在在 viewDidDisappear() 上调用 invalidate() 并再次检查内存图,我们可以看到 DetailViewController 已成功释放。

这对我来说表明 invalidate 是 CADisplayLink 中一个非常重要的方法,应该调用它来 dealloc CADisplayLink 以防止保留周期和内存泄漏。

A 运行 循环保留对添加到其中的任何显示 link 的强引用。请参阅 add(to:forMode:) 文档:

The run loop retains the display link. To remove the display link from all run loops, send an invalidate() message to the display link.

并且显示 link 保持对其 target 的强引用。请参阅 invalidate() 文档:

Removing the display link from all run loop modes causes it to be released by the run loop. The display link also releases the target.

所以,你一定要 invalidate()。如果您使用 self 作为显示 link 的 target,则不能在 deinit 中执行此操作(因为 CADisplayLink 保留对它的目标)。


在视图控制器中执行此操作的常见模式是在 viewDidAppear 中设置显示 link 并在 viewDidDisappear 中删除它。

例如:

private weak var displayLink: CADisplayLink?

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    startDisplayLink()
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    stopDisplayLink()
}

private func startDisplayLink() {
    stopDisplayLink()  // stop previous display link if one happens to be running

    let link = CADisplayLink(target: self, selector: #selector(handle(displayLink:)))
    link.add(to: .main, forMode: .commonModes)
    displayLink = link
}

private func stopDisplayLink() {
    displayLink?.invalidate()
}

@objc func handle(displayLink: CADisplayLink) {
    // do something
}