Why does timer continue to execute after invalidation?
The scheduledTimer(withTimeInterval:repeats:block:)
method:
After
interval
seconds have elapsed, the timer fires, executingblock
.
The invalidate()
method:
Stops the timer from ever firing again
You are correct in your discovery that invalidating a timer will not interrupt a currently executing block, but will only prevent future executions of that block.
Do I need to invalidate/release the timer?
This kind of timer is retained only by the run loop. Since you don't have a pointer to it, you can't invalidate
it. It also is going to retain its target (self
), so it's not possible for self
to deallocate prior to the timer firing. After it fires, the run loop will release it, so it will deallocate, causing it to release self
, and allowing that object to deallocate (assuming there are no other retains on it).
In most case I do not recommend ignoring the return value from scheduledTimerWithTimeInterval:…
because it makes it impossible for you to invalidate
the timer. But if that behavior is exactly what you want (sometimes in global timers created in AppDelegate), then it will work fine with no additional code.
swift invalidate timer doesn't work
The usual way to start and stop a timer safely is
var timer : Timer?
func startTimer()
{
if timer == nil {
timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
}
}
func stopTimer()
{
timer?.invalidate()
timer = nil
}
startTimer()
starts the timer only if it's nil
and stopTimer()
stops it only if it's not nil
.
You have only to take care of stopping the timer before creating/starting a new one.
Difference between weak references in a block and a NSTimer
The whole problem with the selector-based NSTimer
technique is that it establishes a strong reference to the object you pass to it. So whether the variable you used to hold the reference to the target you passed to scheduledTimerWithTimeInterval
was, itself, strong or weak, is immaterial. Assuming the target
reference wasn't nil
by the time the selector-based scheduled timer was scheduled, NSTimer
will establish its own strong reference. The "weak" vs "strong" nature of the references in calling code only dictates where ARC will place its own memory management calls in the caller's code, but the target
is just a simple pointer, and none of this weak vs strong information is conveyed to NSTimer
. The selector-based NSTimer
will establish its own strong reference that is not resolved until the timer is invalidated
.
This is why, when we want to invalidate a timer built via the selector-based method, we have to it in viewDidDisappear
or the like, rather than dealloc
.
Note, scheduledTimerWithTimeInterval
now has a block-based variation for iOS 10 and later, so you can enjoy the weak reference pattern of blocks if you don't have to support earlier iOS versions:
typeof(self) __weak weakSelf = self;
[NSTimer scheduledTimerWithTimeInterval:30 repeats:true block:^(NSTimer * _Nonnull timer) {
// use weakSelf here
}];
Timer is being fired twice and not nilled
Thanks to solenoid and Paulw11 comments, I learned that all executions related to a timer should be done in a serial manner.
Also see invalidate
You must send this message from the thread on which the timer was
installed. If you send this message from another thread, the input
source associated with the timer may not be removed from its run loop,
which could prevent the thread from exiting properly.
So in addition to invalidating and nilling (you don't want to give non-nil
for an object that no longer conveys any meaning), make sure that you:
- use the same thread
- the thread you are using is serial
- depending on where you make changes onto the timer you may or may not need synchronization.
Timer will not Invalidate swift 4
Your problem lies in this part of your code.
Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(timeoutPeriod)), userInfo: nil, repeats: true)
You're creating a Timer but not setting it into the Timer
variable you created var timer = Timer()
To fix this you just have to set your Timer
variable correctly.
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(timeoutPeriod)), userInfo: nil, repeats: true)
How to set the timers in [NSTimer] to nil?
It is because you are in a loop, where you cannot mutate the object that is being iterated.
for timer in timers {
// timer is initialized here like "let timer = timers[index]"
}
Invalidate the the timers in the loop, then set
timers = nil
or
timers.removeAll()
NSTimer doesn't stop with invalidate
Read documentation for NSTimer:
There are three ways to create a timer:
Use the scheduledTimerWithTimeInterval:invocation:repeats: or scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer and schedule it on the current run loop in the default mode.
Use the timerWithTimeInterval:invocation:repeats: or timerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer object without scheduling it on a run loop. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.)
Allocate the timer and initialize it using the initWithFireDate:interval:target:selector:userInfo:repeats: method. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.)
You are using method which already adds it to mainLoop from 1. - you need to remove this line or create a timer with 2. approach and leave manual adding.
Also remember that you must send invalidate message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.
Deinit / Invalidate Timer
When you use a scheduled timer with 'target/selector', it retains its target. More specifically, the Runloop retains scheduled timers, in turn which retain their target.
I use this version, which doesn't retain self:
Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] _ in
self?.doSomethingRegularly()
})
And you'll still need to invalidate the above timer in your deinit as well, otherwise you'll leak the timer (but not your class).
Related Topics
Mkmapview Doesn't Zoom Correctly While User Tracking Mode Is Mkusertrackingmodefollowwithheading
Phimageresultisdegradedkey/Phimagefileurlkey Is Not Found
How to Detect the Scroll Direction from the Uicollectionview
iOS 7.0 and Arc: Uitableview Never Deallocated After Rows Animation
Youtube Video Autoplay Inside Uiwebview
Why Is an Nsdate in a Core Data Managed Object Converted to Nstimeinterval
How to Recover After I Execute :Git Reset --Hard Head
How to Detect "Clear" Notifications
Removing Lagging Latency in Drawing Uibezierpath Smooth Lines in Swift
Offline Crash Reporting in Crashlytics
What Is Difference Between Self.Timer = Nil VS [Self.Timer Invalidate] in iOS
iOS Multiple Right and Left Align on Same Line
How to Differentiate Whether a User Is Tapping the Screen with His Finger or an Apple Pencil