[英]What is difference between self.timer = nil vs [self.timer invalidate] in iOS?
Can anyone explain me self.timer=nil
vs [self.timer invalidate]
?谁能解释我self.timer=nil
与[self.timer invalidate]
?
What exactly happens at the memory location of self.timer
? self.timer
的内存位置究竟发生了什么?
In my code在我的代码中
self.timer=nil
doesn't stops the timer but不会停止计时器但是
[self.timer invalidate]
stops the timer.停止计时器。
If you require my code I will update that too.如果您需要我的代码,我也会更新。
Once you have no need to run timer, invalidate timer object, after that no need to nullify its reference.一旦您不需要运行计时器,就使计时器对象无效,之后无需取消其引用。
This is what Apple documentation says: NSTimer这就是 Apple 文档所说的: NSTimer
Once scheduled on a run loop, the timer fires at the specified interval until it is invalidated.一旦在运行循环上调度,计时器就会在指定的时间间隔内触发,直到它失效。 A non-repeating timer invalidates itself immediately after it fires.非重复定时器在触发后立即失效。 However, for a repeating timer, you must invalidate the timer object yourself by calling its invalidate method.但是,对于重复计时器,您必须自己通过调用其 invalidate 方法来使计时器对象无效。 Calling this method requests the removal of the timer from the current run loop;调用该方法请求从当前运行循环中移除定时器; as a result, you should always call the invalidate method from the same thread on which the timer was installed.因此,您应该始终从安装计时器的同一线程调用 invalidate 方法。 Invalidating the timer immediately disables it so that it no longer affects the run loop.使计时器无效会立即禁用它,以便它不再影响运行循环。 The run loop then removes the timer (and the strong reference it had to the timer), either just before the invalidate method returns or at some later point.然后运行循环移除计时器(以及它对计时器的强引用),要么是在 invalidate 方法返回之前,要么是在稍后的某个时间点。 Once invalidated, timer objects cannot be reused.一旦失效,定时器对象就不能再使用。
There is a key difference not mentioned in the other answers.其他答案中没有提到一个关键区别。
To test this drop the following code in Playground.为了测试这个,在 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
So let me ask you a question.所以让我问你一个问题。 At the last line of the code I just set person
to nil
.在代码的最后一行,我只是将person
设置为nil
。 That means the person
object is deallocated and all its properties are set to nil
and removed from memory.这意味着person
对象被释放,它的所有属性都设置为nil
并从内存中删除。 Right?对?
An object is deallocated as long as no other object is holding a strong a reference to it.只要没有其他对象持有对它的强引用,一个对象就会被释放。 In our case the timer
is still holding a strong reference to person, because the run-loop has a strong reference to the timer § hence the person
object will not get deallocated.在我们的例子中, timer
仍然持有对 person 的强引用,因为运行循环对定时器§有一个强引用,因此person
对象不会被释放。
The result of the above code is that it still continues to execute!上面代码的结果是还是继续执行! Let's fix it.让我们修复它。
Let's set the timer to nil
.让我们将计时器设置为nil
。 This should remove the strong reference of timer
pointing to person
.这应该删除指向person
的timer
的强引用。
var person : Person? = Person(age: 0)
let _ = person?.timer
person?.timer = nil
person = nil
WRONG!错误的! We only removed our pointer to the timer
.我们只删除了指向timer
的指针。 Yet the result of the above code is just like our initial attempt.然而上面代码的结果就像我们最初的尝试一样。 It still continues to execute...because the run loop is still targeting/referencing self
.它仍然继续执行......因为运行循环仍然针对/引用self
。
So what do we need to do?那么我们需要做什么呢?
Glad you asked.很高兴你问。 We must invalidate
the timer!我们必须invalidate
计时器invalidate
!
var person : Person? = Person(age: 0)
let _ = person?.timer
person?.timer = nil
person?.timer?.invalidate()
person = nil
This looks better, but it's still wrong.这看起来更好,但它仍然是错误的。 Can you guess why?你能猜到为什么吗?
I'll give you a hint.我给你一个提示。 See code below 👇.看下面的代码👇。
var person : Person? = Person(age: 0)
let _ = person?.timer
person?.timer?.invalidate()
person?.timer = nil
person = nil
// person was deallocated
Our 4th attempt was just like our 3rd attempt, just that the sequence of code was different.我们的第四次尝试和我们的第三次尝试一样,只是代码的顺序不同。
person?.timer?.invalidate()
removes the run loop's strong reference to its target ie self
and now if a pointer to person
is removed...our person object gets deallocated! person?.timer?.invalidate()
删除了运行循环对其目标即self
的强引用,现在如果指向person
的指针被删除......我们的 person 对象将被释放!
The attempt below is also correct:下面的尝试也是正确的:
var person : Person? = Person(age: 0)
let _ = person?.timer
person?.timer?.invalidate()
person = nil
// person was deallocated
Notice that in our 5th attempt we didn't set the timer to nil
.请注意,在我们的第 5 次尝试中,我们没有将计时器设置为nil
。 But Apple recommends that we do such:但是 Apple 建议我们这样做:
Once invalidated, timer objects cannot be reused.一旦失效,定时器对象就不能再使用。
Setting it to nil
is also an indicator that for other parts of code.将其设置为nil
也是代码其他部分的指示符。 It helps up so that we can check against it and if it wasn't nil
then we'd know the timer is still valid and also to not have a meaningless object around.它有助于我们检查它,如果它不是nil
那么我们就会知道计时器仍然有效,并且周围没有无意义的对象。
After invalidating the timer you should assign
nil
to the variable otherwise the variable is left pointing to a useless timer.在使计时器无效后,您应该将nil
分配给变量,否则该变量将指向一个无用的计时器。 Memory management and ARC have nothing to do with why you should set it tonil
.内存管理和 ARC 与为什么要将其设置为nil
无关。 After invalidating the timer,self.timer
is now referencing a useless timer.在使计时器无效后,self.timer
现在正在引用一个无用的计时器。 No further attempts should be made to use that value.不应进一步尝试使用该值。 Setting it tonil
ensures that any further attempts to access self.timer will result innil
将其设置为nil
可确保任何进一步尝试访问 self.timer 都将导致nil
That being said I think isValid
is a more meaningful approach just as isEmpty
is more meaningful and efficient than doing array.count == 0
...话虽如此,我认为isValid
是一种更有意义的方法,就像isEmpty
比执行array.count == 0
更有意义和更有效array.count == 0
......
Because we need a pointer to the timer so we can invalidate it.因为我们需要一个指向计时器的指针,以便我们可以使其无效。 If we set that pointer to nil
then we loose our pointer to it.如果我们将该指针设置为nil
那么我们就会失去指向它的指针。 We lose it while the run-loop has still maintained its pointer to it!当运行循环仍然保持指向它的指针时,我们丢失了它! So if we ever wanted to turn off the timer we should invalidate
it BEFORE we lose our reference to it (ie before we set its pointer to nil
) otherwise it becomes an abandoned memory ( not leak ).因此,如果我们想关闭计时器,我们应该在我们失去对它的引用之前(即在我们将其指针设置为nil
)使其invalidate
,否则它将成为废弃的内存( 而不是泄漏)。
invalidate
.要正确停止计时器,您必须使用invalidate
。 Do nil
the timer
before you invalidate
it.在使timer
invalidate
之前invalidate
其置nil
。timer
, set it to nil
so it doesn't get reused.在您使timer
无效后,将其设置为nil
以免被重用。invalidate
will remove the run loop's pointer to self
.调用invalidate
将删除运行循环指向self
的指针。 Only then the object containing the timer will be released.只有这样,包含计时器的对象才会被释放。So how does this apply when I'm actually building an application?那么当我实际构建应用程序时,这如何适用?
If your viewController has person
property and then your popped this viewController off your navigation stack then your viewController will get deallocated.如果您的 viewController 具有 person
属性,然后您将这个 viewController 从导航堆栈中弹出,那么您的 viewController 将被释放。In its deinit
method you must invalidate the person's timer.在其 deinit
方法中,您必须使该人的计时器无效。Otherwise your person instance is kept in memory because of the run loop and its timer action will still want to execute!否则,由于运行循环,您的 person 实例将保留在内存中,并且它的计时器操作仍要执行! This can lead to a crash!这可能会导致崩溃!
Correction:更正:
Thanks to Rob's answer感谢罗布的回答
If you're dealing with repeating [NS]Timers, don't try to invalidate them in dealloc of the owner of the [NS]Timer because the dealloc obviously will not be called until the strong reference cycle is resolved.如果您正在处理重复的 [NS] 计时器,请不要尝试在 [NS] 计时器的所有者的 dealloc 中使它们无效,因为在解决强引用循环之前,dealloc 显然不会被调用。 In the case of a UIViewController, for example, you might do it in viewDidDisappear例如,在 UIViewController 的情况下,您可以在 viewDidDisappear 中执行此操作
That being said viewDidDisappear
may not always be the correct place since viewDidDisappear
also gets called if you just push a new viewController on top of it.话虽如此, viewDidDisappear
可能并不总是正确的位置,因为如果你只是在它上面推一个新的 viewController, viewDidDisappear
也会被调用。 You should basically do it from a point that it's no longer needed.你基本上应该从不再需要它的角度来做。 You get the idea...你明白了...
§: Because the run loop maintains the timer, from the perspective of object lifetimes there's typically no need to keep a reference to a timer after you've scheduled it . §:因为运行循环维护计时器,从对象生命周期的角度来看,通常不需要在您安排计时器后保留对计时器的引用。 (Because the timer is passed as an argument when you specify its method as a selector, you can invalidate a repeating timer when appropriate within that method.) In many situations, however, you also want the option of invalidating the timer—perhaps even before it starts. (因为当您将计时器的方法指定为选择器时,计时器作为参数传递,您可以在该方法中适当时使重复计时器无效。)但是,在许多情况下,您还需要使计时器无效的选项 - 甚至可能在此之前开始。 In this case, you do need to keep a reference to the timer, so that you can stop it whenever appropriate.在这种情况下,您确实需要保留对计时器的引用,以便您可以在适当的时候停止它。
With all the credit going to my colleague Brandon:所有的功劳都归功于我的同事布兰登:
Even if you don't have a repeating timer, the Runloop [as mentioned within the docs] will hold a strong reference to your target if you use the selector function , until it fires, after that it will release it.即使您没有重复计时器,如果您使用选择器函数,Runloop [如文档中所述] 将持有对您的目标的强引用,直到它触发,之后它将释放它。
However if you use the block based function then as long as you point weakly to self inside your block then the runloop will not retain self
.但是,如果您使用基于块的函数,那么只要您在块内弱指向 self ,那么 runloop 就不会保留self
。 However it will continue to execute, due to the lack of calling invalidate
但是它会继续执行,因为没有调用invalidate
If you don't use [weak self]
then the block based will act just like the selector kind, that it will deallocate self
after it has been fired.如果你不使用[weak self]
那么基于块的行为就像选择器类型一样,它会在它被触发后释放self
。
Paste the following code in Playground and see the difference.将以下代码粘贴到 Playground 中,看看有什么不同。 The selector version will be deallocated after it fires.选择器版本将在触发后被释放。 The block base will be deallocated upon deallocation.块基将在解除分配时解除分配。 Basically the lifecycle of one is governed by the runloop while for the other it's governed by the object itself基本上,一个的生命周期由 runloop 管理,而另一个则由对象本身管理
@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))
First of all, invalidate
is a method of NSTimer
class which can use to stop currently running timer.首先, invalidate
是NSTimer
类的一个方法,可以用来停止当前正在运行的计时器。 Where when you assign nil
to any object then, in an ARC environment the variable will release the object.当您将nil
分配给任何对象时,在 ARC 环境中,该变量将释放该对象。
Its important to stop running timer when you don't longer need, so we write [timer invalidate]
and then we write timer = nil;
当你不再需要时停止运行计时器很重要,所以我们写[timer invalidate]
然后我们写timer = nil;
to make sure it'll loose its address from memory and later time you can recreate the timer.以确保它会从内存中丢失其地址,稍后您可以重新创建计时器。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.