What is the difference between self.timer = nil and [self.timer invalidate] in iOS?

Can someone explain me self.timer=nil vs [self.timer invalidate] ?

What exactly happens in the memory cell self.timer ?

In my code

 self.timer=nil 

doesn't stop the timer but

 [self.timer invalidate] 

stops the timer.

If you need my code, I will update it too.

+10
memory-management ios objective-c nstimer strong-references
source share
3 answers

As soon as you do not need to start the timer, an invalid timer object, after which there is no need to cancel its link.

Here's what the Apple documentation says: NSTimer

After the scheduled start-up cycle, the timer runs at the set until it is declared invalid. A non-repeating timer is invalid immediately after its occurrence. However, for a repeating timer, you must invalidate the timer object yourself by invoking its invalid method. Calling this method requires removing the timer from the current loop; as a result, you should always call the invalidate method from the same thread on which the timer was set. An invalid timer immediately disables it so that it no longer affects the execution cycle. Then the start loop removes the timer (and a strong reference to the timer), either immediately before invalidate returns or at some later point. Once this is invalidated, timer objects cannot be reused.

+11
source share

There is a key difference not mentioned in other answers.

To verify this, add the following code to the 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. In the last line of code, I just set person to nil . This means that the person object is freed and all its properties are set to nil and removed from memory. Correctly?

An object is freed until another object has a strong reference to it. In our case, the timer still holds a strong reference to the person, because the execution loop has a strong reference to the timer Β§ , therefore, the person object will not be freed.

The result of the above code is that it still continues to execute! Let's fix it.


2nd attempt:

Let me set the timer to nil . This should remove the strong timer link pointing to person .

 var person : Person? = Person(age: 0) let _ = person?.timer person?.timer = nil person = nil 

WRONG! We just deleted our timer pointer. Nevertheless, the result of the above code is similar to our initial attempt. it still continues to execute ... because the execution loop still stores a pointer to it.


So what do we need to do?

Glad you asked. We must invalidate timer!

3rd attempt:

 var person : Person? = Person(age: 0) let _ = person?.timer person?.timer = nil person?.timer?.invalidate() person = nil 

It looks better, but still wrong. Can you guess why?

I will 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 the same as our 3rd attempt, just the code sequence was different.

person?.timer?.invalidate() removes the strong link of the run loop, and now if the pointer to person removed ... it is freed.!

The attempt below is also correct:

5th attempt (correct)

 var person : Person? = Person(age: 0) let _ = person?.timer person?.timer?.invalidate() person = nil // person was deallocated 

Please note that in our 5th attempt, we did not set the timer to nil . How is it not necessary. Setting it to nil is just an indicator that we can use in other lines of code. This helps us verify this, and if it were not nil , we would know that the timer is still active and also has no meaningful object.

After the timer is canceled, you must assign nil variable; otherwise, the variable remains indicating a useless timer. Memory Management and ARC have nothing to do with why you should install it nil . After the timer is canceled, self.timer now refers to the useless timer. No further attempt should be made to use this value. Setting it to nil ensures that any further access attempts. The result of self.timer will be nil

from rmaddy comment above

In doing so, I think that isValid is a more meaningful approach, just as isEmpty is more meaningful and efficient than array.count == 0 ...


So why is the 3rd attempt wrong?

Because we need a pointer to a timer so that we can make it invalid. If we set this pointer to nil , we lose the pointer to it. We lose it while the execution loop still maintains a pointer to it! Therefore, if we ever wanted to disable the timer, we must invalidate it BEFORE we lose our link to it (that is, before we set its pointer to nil ), and then it becomes an abandoned memory ( do not leak ).

Output:

  • To get rid of the timer, nil not required. In fact, it is harmful if you do this before invalidate your timer.
  • Calling invalidate will remove the run loop pointer to it. Only then will the object containing the timer be freed.

So how is this applicable when I actually build the application?

If your viewController has a person property, and you popped that viewController from the navigation stack, then your viewController will be freed. In this deinit method, you must invalidate the person’s timer. Otherwise, your user instance will be stored in memory due to the execution cycle , and its action will continue to be executed! This may cause a malfunction!

Correction:

Thanks Rob answer

If you are dealing with duplicate [NS] timers, do not try to invalidate them in the dealloc of the owner of the [NS] timer, as it is obvious that dealloc will not be called until a strong reference loop has been resolved. For example, in the case of a UIViewController, you can do this in viewDidDisappear

However, viewDidDisappear not always be the right place, since viewDidDisappear also called if you just put a new viewController on top of it. You should basically do this from the moment you no longer need it. Did you understand...


Β§: since the execution loop supports the timer in terms of the lifetime of the object, there is usually no need to keep a reference to the timer after you have scheduled it. (Because the timer is passed as an argument when you specify its method as a selector, you can invalidate the repeating timer when appropriate in this method.) In many situations, however, you also want the timer cancellation option - perhaps even before how it will start. In this case, you need to save the link to the timer so that you can stop it whenever appropriate.


Thanks to my colleague Brandon:

Professional advice:

Even if you don't have a repeating timer, Runloop [as mentioned in the documentation] will contain a strong link to your target, if you use the select function until it works, then it will issue This.

However, if you use a block function , then while you are weakly pointing to yourself inside your block, you need not worry about calling invalidate against the timer. It will just leave, as it was supported by the target, not runloop. If you do not use [weak self] , then the block-based one will act in the same way as the selector type, so that it frees self after it starts.

Paste the following code into the Playground and see the difference. The selector version will be released after launch . The base of blocks will be released after release. In fact, the life cycle of one of them is controlled by the execution cycle, and for the other by the object itself.

 @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)) 
+4
source share

First of all, invalidate is an NSTimer class method that can be used to stop the current timer. Where, when you assign nil any object, then in the ARC environment, the variable will free the object.

It is important to stop the timer from starting when you no longer need it, so we write [timer invalidate] and then write timer = nil; so that he lost his address from memory, and later you can restore the timer.

+1
source share

All Articles