谁能解释我self.timer=nil
vs [self.timer invalidate]
?
的内存位置究竟发生了什么self.timer
?
在我的代码中
self.timer=nil
不会停止计时器,但
[self.timer invalidate]
停止计时器。
如果您需要我的代码,我也会更新。
谁能解释我self.timer=nil
vs [self.timer invalidate]
?
的内存位置究竟发生了什么self.timer
?
在我的代码中
self.timer=nil
不会停止计时器,但
[self.timer invalidate]
停止计时器。
如果您需要我的代码,我也会更新。
一旦您不需要运行计时器,则使计时器对象无效,之后无需取消其引用。
这就是 Apple 文档所说的:NSTimer
一旦安排在运行循环上,计时器就会以指定的时间间隔触发,直到它失效。非重复计时器在触发后立即使自己失效。但是,对于重复计时器,您必须自己通过调用其 invalidate 方法来使计时器对象无效。调用此方法请求从当前运行循环中移除计时器;因此,您应该始终从安装了计时器的同一线程调用 invalidate 方法。使计时器无效会立即将其禁用,以便它不再影响运行循环。然后运行循环删除计时器(以及它对计时器的强引用),或者在 invalidate 方法返回之前或稍后的某个时间点。一旦失效,定时器对象就不能被重用。
其他答案中没有提到一个关键区别。
要对此进行测试,请将以下代码放入 Playground。
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
class Person{
var age = 0
lazy var timer: Timer? = {
let _timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
return _timer
}()
init(age: Int) {
self.age = age
}
@objc func fireTimer(){
age += 1
print("age: \(age)")
}
deinit {
print("person was deallocated")
}
}
// attempt:
var person : Person? = Person(age: 0)
let _ = person?.timer
person = nil
所以让我问你一个问题。在代码的最后一行,我只是设置person
为nil
. 这意味着person
对象被释放,它的所有属性都被设置到nil
内存并从内存中删除。正确的?
只要没有其他对象持有对它的强引用,一个对象就会被释放。在我们的例子中,timer
仍然持有对 person 的强引用,因为run- loop对 timer 有一个强引用,因此person
对象不会被释放。
上面代码的结果是它仍然继续执行!让我们修复它。
让我们将计时器设置为nil
。这应该删除timer
指向的强引用person
。
var person : Person? = Person(age: 0)
let _ = person?.timer
person?.timer = nil
person = nil
错误的!我们只删除了指向timer
. 然而上面代码的结果就像我们最初的尝试一样。它仍然继续执行......因为运行循环仍在定位/引用self
。
那么我们需要做什么呢?
很高兴你问。我们必须invalidate
计时!
var person : Person? = Person(age: 0)
let _ = person?.timer
person?.timer = nil
person?.timer?.invalidate()
person = nil
这看起来更好,但它仍然是错误的。你能猜到为什么吗?
我给你一个提示。请参阅下面的代码。
var person : Person? = Person(age: 0)
let _ = person?.timer
person?.timer?.invalidate()
person?.timer = nil
person = nil
// person was deallocated
我们的第 4 次尝试和第 3 次尝试一样,只是代码的顺序不同。
person?.timer?.invalidate()
删除运行循环 对其目标的强引用,即self
,现在如果一个指向person
被删除...我们的人对象被释放!
下面的尝试也是正确的:
var person : Person? = Person(age: 0)
let _ = person?.timer
person?.timer?.invalidate()
person = nil
// person was deallocated
请注意,在我们的第 5 次尝试中,我们没有将计时器设置为nil
. 但 Apple 建议我们这样做:
一旦失效,定时器对象就不能被重用。
请参阅任务管理 - 计时器
将其设置nil
为也是其他代码部分的指标。它有助于我们检查它,如果不是,nil
那么我们就会知道计时器仍然有效,并且周围没有无意义的对象。
使计时器无效后,您应该将其分配
nil
给变量,否则该变量将指向无用的计时器。内存管理和 ARC 与为什么要将其设置为nil
. 在使计时器无效后,self.timer
现在正在引用一个 无用的计时器。不应再尝试使用该值。将其设置为nil
可确保进一步尝试访问 self.timer 将导致nil
来自rmaddy上面的评论
话虽如此,我认为这isValid
是一种更有意义的方法,就像isEmpty
比做array.count == 0
...更有意义和有效一样
因为我们需要一个指向计时器的指针,所以我们可以使其无效。如果我们将该指针设置为 ,nil
那么我们将失去指向它的指针。当运行循环仍然保持其指向它的指针时,我们丢失了它!因此,如果我们想要关闭计时器,我们应该在失去invalidate
对它的引用之前(即在我们将其指针设置为 之前nil
)关闭它,否则它会成为废弃的内存(而不是泄漏)。
invalidate
. 不要nil
在timer
你面前invalidate
呢。timer
,将其设置为nil
不会被重用。invalidate
将删除运行循环的指针self
。只有这样,包含计时器的对象才会被释放。那么这在我实际构建应用程序时如何应用?
如果您的 viewController 具有person
属性,然后您将该 viewController 从导航堆栈中弹出,那么您的 viewController 将被释放。在其deinit
方法中,您必须使该人的计时器无效。否则,由于运行循环,您的 person 实例将保留在内存中,并且它的计时器操作仍将要执行!这可能导致崩溃!
更正:
感谢Rob 的回答
如果您正在处理重复的 [NS]Timer,请不要尝试在 [NS]Timer 的所有者的 dealloc 中使它们无效,因为在解决强引用循环之前显然不会调用 dealloc。例如,在 UIViewController 的情况下,您可以在 viewDidDisappear 中执行此操作
话虽这么说viewDidDisappear
可能并不总是正确的地方,因为viewDidDisappear
如果你只是在它上面推一个新的 viewController 也会被调用。您基本上应该从不再需要它的角度进行操作。你明白了……
§: 因为运行循环维护计时器,所以从对象生命周期的角度来看,通常不需要在您安排好计时器后保留对计时器的引用。(因为当您将计时器的方法指定为选择器时,计时器作为参数传递,您可以在该方法中适当时使重复计时器无效。)然而,在许多情况下,您还需要使计时器无效的选项——甚至可能在此之前开始。在这种情况下,您确实需要保留对计时器的引用,以便您可以在适当的时候停止它。
归功于我的同事布兰登:
即使您没有重复计时器,如果您使用选择器函数,Runloop [如文档中所述] 将保持对您的目标的强引用,直到它触发,之后它将释放它。
但是,如果您使用基于块的函数,那么只要您在块内弱指向 self ,那么 runloop 将不会保留self
。但是由于缺少调用,它将继续执行invalidate
如果你不使用[weak self]
,那么基于块的行为就像选择器类型一样,它会self
在它被触发后释放。
将以下代码粘贴到 Playground 中并查看差异。选择器版本将在触发后被释放。块基将在解除分配时被解除分配。基本上,一个的生命周期由运行循环控制,而另一个则由对象本身控制
@objc class MyClass: NSObject {
var timer: Timer?
func startSelectorTimer() {
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(MyClass.doThing), userInfo: nil, repeats: false)
}
func startBlockTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false, block: { [weak self] _ in
self?.doThing()
})
}
@objc func doThing() {
print("Ran timer")
}
deinit {
print("My Class deinited")
}
}
var mySelectorClass: MyClass? = MyClass()
mySelectorClass?.startSelectorTimer()
mySelectorClass = nil // Notice that MyClass.deinit is not called until after Ran Timer happens
print("Should have deinited Selector timer here")
RunLoop.current.run(until: Date().addingTimeInterval(7))
print("---- NEW TEST ----")
var myBlockClass: MyClass? = MyClass()
myBlockClass?.startBlockTimer()
myBlockClass = nil // Notice that MyClass.deinit IS called before the timer finishes. No need for invalidation
print("Should have deinited Block timer here")
RunLoop.current.run(until: Date().addingTimeInterval(7))
首先,invalidate
是一个NSTimer
类的方法,可以用来停止当前正在运行的定时器。那么,当您分配nil
给任何对象时,在 ARC 环境中,变量将释放该对象。
当您不再需要时停止运行计时器很重要,因此我们编写[timer invalidate]
然后我们编写timer = nil;
以确保它会从内存中丢失其地址,稍后您可以重新创建计时器。