简体   繁体   English

iOS 中的 self.timer = nil 与 [self.timer invalidate] 有什么区别?

[英]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 中删除以下代码。

1st Attempt:第一次尝试:

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.让我们修复它。


2nd Attempt:第二次尝试:

Let's set the timer to nil .让我们将计时器设置为nil This should remove the strong reference of timer pointing to person .这应该删除指向persontimer的强引用。

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

3rd Attempt:第三次尝试:

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 👇.看下面的代码👇。


4th Attempt (correct)第四次尝试(正确)

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:下面的尝试也是正确的:

5th Attempt (correct)第 5 次尝试(正确)

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.一旦失效,定时器对象就不能再使用。

See Task Management - Timer请参阅任务管理 - 计时器

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 to nil .内存管理和 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 to nil ensures that any further attempts to access self.timer will result in nil将其设置为nil可确保任何进一步尝试访问 self.timer 都将导致nil

from rmaddy's comment above来自 rmaddy 上面的评论

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 ......


So why is 3rd attempt not correct?那么为什么第三次尝试不正确呢?

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 ,否则它将成为废弃的内存( 而不是泄漏)。

Conclusion:结论:

  • To get stop a timer correctly you must use invalidate .要正确停止计时器,您必须使用invalidate Do nil the timer before you invalidate it.在使timer invalidate之前invalidate其置nil
  • After you've invalidated a timer , set it to nil so it doesn't get reused.在您使timer无效后,将其设置为nil以免被重用。
  • Calling 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:所有的功劳都归功于我的同事布兰登:

Pro Tip:专家提示:

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.首先, invalidateNSTimer类的一个方法,可以用来停止当前正在运行的计时器。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM