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") } }
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
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
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