【问题标题】:How to stop class level timer ? - Swift如何停止班级级别计时器? - 斯威夫特
【发布时间】:2017-09-05 06:50:48
【问题描述】:

今天我正在阅读swift编程指南here中的“闭包的强参考循环”,我的问题就是基于此。

我在导航控制器中嵌入了两个视图控制器,我在第一个视图控制器中没有任何内容,只是用来连接到 secondviewcontroller,并且我在 secondViewcontroller aka ViewController2 中有以下代码。

代码 1:

class ViewController2: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()

        Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { (_) in
            print("timer")
        }

    }

}

弹出到第一个视图控制器后,此计时器不会结束,它会不断打印“计时器”。如何停止?,下面是调试内存图的屏幕截图。

如上图我们可以看到没有第二个视图控制器的实例,但是定时器会连续执行!

如果我在计时器关闭时持有或使用 self 的强 ref,那么我相信没关系。但是根据上面的代码,我没有持有或使用任何强引用,它也显示在调试内存图中(pop 后没有 secondviewcontroller 的引用)。

如果我使用下面的代码,那么我接受计时器正在运行,因为按照下面的代码强引用周期。

代码 2:

class ViewController2: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()

        Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { (_) in
            print("\(self.view.tag)")
        }

    }

}

下面是调试内存图,弹出到第一个视图控制器后,可以看到第二个视图控制器引用仍然存在

根据我的代码 2 部分,我可以接受它正在运行,因为 viewcontroller2 仍在内存中并正在运行。但是在我的代码 1 部分中,没有任何迹象表明 viewcontrller2 在内存中并且它的计时器有效吗?为什么 ? 如何在不将其作为实例的情况下停止此操作。

【问题讨论】:

  • 这里有什么问题?您想知道为什么在第二个代码中的内存中保留了对 secondVC 的引用,为什么不在第一个代码中?或者你只需​​要知道当你弹出到 First VC 时如何停止时间?
  • 是的,当你跳到First VC时如何停止时间
  • 你确定你已经正确地弹回了第一个 VC 吗?你确定你没有将另一个新的 VC1 推到导航堆栈上吗?因为在最后一张图中,说明VC2还是被UINavigationController保留的。
  • 我的问题是关于代码 1 部分,我正在显示代码 2 部分以供参考(我接受的内容)
  • 当你使用代码 1 时,看看是什么保留了ViewController2UINavigationController 还保留它吗?

标签: ios swift memory-management timer closures


【解决方案1】:

您可以在 SecondVC 弹出之前停止计时器

你需要实例变量

var timer : Timer;

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if timer != nil && self.isMovingFromParentViewController {
        timer.invalidate()
        timer = nil
    }
}

编辑:

好的,我接受,但是为什么 xcode 没有在内存图中显示它。作为 没有 viewcontroller2 的实例。

所以基本上你的问题是你想知道为什么你的第一个代码中没有 ViewController2 引用,以及为什么当你在内存图中使用第二个代码时有 ViewController2 引用:)

什么是块?

为了理解它,你必须理解块/闭包的概念。 块/闭包只不过是堆内存中的引用计数对象之类的结构。

当您将块作为参数传递时,您会将堆中的这个对象从一个函数传递给另一个函数。

这些是引用计数对象,因此 ARC 的所有规则也适用于它们。

上下文捕获

闭包与其他当代部分(实例和静态方法)不同的唯一原因是 ContextCapturing。块能够捕获在其上下文之外声明的变量,而实例和静态方法则不能。

什么是上下文捕获?

每次编译器看到闭包/块的语法时,它都会将闭包中的所有代码复制到堆中的一个对象中。除此之外,它还会复制在其中访问的所有变量。这就是闭包能够访问在其范围之外声明的变量的原因。

你为什么要告诉我这些?

在你的代码中

    Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { (_) in
        print("\(self.view.tag)")
    }

当你在闭包中访问 self 时,你的第二个 VC 被复制到堆内存中的闭包对象。因为它的第二个 VC 的强引用引用计数增加了 +1。

因此,当您点击返回按钮时,不会释放第二个 VC,因为 ARC 仅在其引用计数为 0 时删除对象。

在你的代码中

    Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { (_) in
        print("timer")
    }

你还没有访问过 self 所以当你的 SecondVC 引用计数永远不会改变它的原始值时。因此,当您点击后退按钮时,第二个 VC 会调用 deist

这就是为什么您在第一个代码中看不到第二个 VC 的引用,而您在第二个代码中看到它。

为什么你看到定时器的参考已经被 Sweeper 在他的回答中解释了:) 没有必要再解释了

【讨论】:

  • 我还问过“如何在不使其全球化的情况下阻止它”,您可以在我的问题中找到
  • @anand-suthar :你真的需要在你的编码术语上工作。我还没有将计时器设为全局实例变量。当我将其声明为静态并将其作为 SecondVC.timer 访问时,它变为全局
  • invalidate() 是在 Timer 上声明的实例方法。 Timer 类中没有静态方法可以使所有计时器失效。
  • 好的,但我不想创建实例。我想用作类级别的函数。 (头少)
  • @anand-suthar : Timer 没有任何静态方法来取消计时器伙伴:) invalidate 只是一个实例方法:) 除非你编写自己的计时器类你不能这样做:) 所以基本上你不能
【解决方案2】:

您只能通过将计时器设为类级变量并在viewDidDisappear 中调用invalidate 或类似方法来从内存中删除计时器。

计时器的工作方式是,当您安排计时器时,CFTimer 会添加到CFRunLoop,然后会创建一个称为NSTimerBlockTarget 的东西。您可以从内存图中看到这一点:

只要CFTimer 还活着,计时器就不会停止。当你调用invalidate 时,它会从运行循环中移除计时器。

【讨论】:

    猜你喜欢
    • 2015-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-22
    • 2017-03-24
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多