A lot of people when creating a Timer app or a StopWatch, or similar uses this function:
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true);
in order to update the timer using a user defined function updateTimer that gets called every timeInterval.
But my question is whether using such function could give a non accurate timer, since we call a function that is user defined and we don't count the overheads that there may be, because of our implementation. And if we have loops or other stuff going on, am I right that this isn't the best approach?
I'm wondering because in my case I would need to use this function to increment a variable timer, and also another function that computes the string of the timer to display every time the updateTimer gets called.
What would be the better and most accurate approach to implement a timer? Maybe using NSDate?
Related
While a UIScrollView (or a derived class thereof) is scrolling, it seems like all the NSTimers that are running get paused until the scroll is finished.
Is there a way to get around this? Threads? A priority setting? Anything?
An easy & simple to implement solution is to do:
NSTimer *timer = [NSTimer timerWithTimeInterval:...
target:...
selector:....
userInfo:...
repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
For anyone using Swift 3
timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: aSelector,
userInfo: nil,
repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
tl;dr the runloop is handing scroll related events. It can't handle any more events — unless you manually change the timer's config so the timer can be processed while runloop is handling touch events. OR try an alternate solution and use GCD
A must read for any iOS developer. Lots of things are ultimately executed through RunLoop.
Derived from Apple's docs.
What is a Run Loop?
A run loop is very much like its name sounds. It is a loop your thread
enters and uses to run event handlers in response to incoming events
How delivery of events are disrupted?
Because timers and other periodic events are delivered when you run
the run loop, circumventing that loop disrupts the delivery of those
events. The typical example of this behavior occurs whenever you
implement a mouse-tracking routine by entering a loop and repeatedly
requesting events from the application. Because your code is grabbing
events directly, rather than letting the application dispatch those
events normally, active timers would be unable to fire until after
your mouse-tracking routine exited and returned control to the
application.
What happens if timer is fired when run loop is in the middle of execution?
This happens A LOT OF TIMES, without us ever noticing. I mean we set the timer to fire at 10:10:10:00, but the runloop is executing an event which takes till 10:10:10:05, hence the timer is fired 10:10:10:06
Similarly, if a timer fires when the run loop is in the middle of
executing a handler routine, the timer waits until the next time
through the run loop to invoke its handler routine. If the run loop is
not running at all, the timer never fires.
Would scrolling or anything that keeps the runloop busy shift all the times my timer is going to fire?
You can configure timers to generate events only once or repeatedly. A
repeating timer reschedules itself automatically based on the
scheduled firing time, not the actual firing time. For example, if a
timer is scheduled to fire at a particular time and every 5 seconds
after that, the scheduled firing time will always fall on the original
5 second time intervals, even if the actual firing time gets delayed.
If the firing time is delayed so much that it misses one or more of
the scheduled firing times, the timer is fired only once for the
missed time period. After firing for the missed period, the timer is
rescheduled for the next scheduled firing time.
How can I change the RunLoops's mode?
You can't. The OS just changes itself for you. e.g. when user taps, then the mode switches to eventTracking. When the user taps are finished, the mode goes back to default. If you want something to be run in a specific mode, then it's up to you make sure that happens.
Solution:
When user is scrolling the the Run Loop Mode becomes tracking. The RunLoop is designed to shifts gears. Once the mode is set to eventTracking, then it gives priority (remember we have limited CPU cores) to touch events. This is an architectural design by the OS designers.
By default timers are NOT scheduled on the tracking mode. They are scheduled on:
Creates a timer and schedules it on the current run loop in the
default mode.
The scheduledTimer underneath does this:
RunLoop.main.add(timer, forMode: .default)
If you want your timer to work when scrolling then you must do either:
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self,
selector: #selector(fireTimer), userInfo: nil, repeats: true) // sets it on `.default` mode
RunLoop.main.add(timer, forMode: .tracking) // AND Do this
Or just do:
RunLoop.main.add(timer, forMode: .common)
Ultimately doing one of the above means your thread is not blocked by touch events.
which is equivalent to:
RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .eventTracking)
RunLoop.main.add(timer, forMode: .modal) // This is more of a macOS thing for when you have a modal panel showing.
Alternative solution:
You may consider using GCD for your timer which will help you to "shield" your code from run loop management issues.
For non-repeating just use:
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
// your code here
}
For repeating timers use:
See how to use DispatchSourceTimer
Digging deeper from a discussion I had with Daniel Jalkut:
Question: how does GCD (background threads) e.g. a asyncAfter on a background thread get executed outside of the RunLoop? My understanding from this is that everything is to be executed within a RunLoop
Not necessarily - every thread has at most one run loop, but can have zero if there's no reason to coordinate execution "ownership" of the thread.
Threads are an OS level affordance that gives your process the ability to split up its functionality across multiple parallel execution contexts. Run loops are a framework-level affordance that allows you to further split up a single thread so it can be shared efficiently by multiple code paths.
Typically if you dispatch something that gets run on a thread, it probably won't have a runloop unless something calls [NSRunLoop currentRunLoop] which would implicitly create one.
In a nutshell, modes are basically a filter mechanism for inputs and timers
Yes, Paul is right, this is a run loop issue. Specifically, you need to make use of the NSRunLoop method:
- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
You have to run another thread and another run loop if you want timers to fire while scrolling; since timers are processed as part of the event loop, if you're busy processing scrolling your view, you never get around to the timers. Though the perf/battery penalty of running timers on other threads might not be worth handling this case.
This is the swift version.
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
for anyone use Swift 4:
timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .common)
Tested in swift 5
var myTimer: Timer?
self.myTimer= Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
//your code
}
RunLoop.main.add(self.myTimer!, forMode: .common)
Specifically, when a task can be complemented with or without selectors, is there any (objective) reason to prefer one way over the other?
For instance, an NSTimer can run a method on an interval in two ways:
A) Using Selectors:
let timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction()), userInfo: nil, repeats: true)
dynamic func timerAction() {
print("foo")
}
B) Without Selectors:
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.timerAction()
}
func timerAction() {
print("foo")
}
Both versions are entirely functional, so is there any (objective) reason to prefer one over the other?
The target/selector design is baggage left over from the earlier Objective-C, pre-block days of the APIs.
If Apple were to redo all of those APIs today, the whole target/selector design would disappear and all be replaced with blocks/closures. This idea is supported by newer Apple APIs. Example - the old UIAlertView used a delegate. The newer UIAlertController only uses closures/blocks for the alert actions.
Using closures (Swift) or blocks (Objective-C) is much preferred over using the target/selector approach.
Here are problems with using target/selector:
The dreaded "unrecognized selector" error.
The need to create an extra, top-level method just for the selector.
The possibility of specifying the wrong parameters and data types on the selector method.
Losing all of the current context you have at the point you setup the object that needs the target/selector.
The opposite of all of those are the benefits of using the closure/block form.
It's possible, that over time, many of the delegate versions of APIs will become deprecated and replaced with closure/block versions. If you have the choice, I'd suggest using the closure/block form now to avoid the need to update later, not to mention it's just the simpler solution.
I'm creating new game with Swift and SpriteKit.
I'm using NSTimer for create a new objects on scene.
For example:
enemyTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector:#selector(GameScene.enemyAppear),userInfo: nil, repeats: true)
But I don't need to use time interval. I want to use distance interval. How can I do this? For example: 50 dots between nodes.
Sorry for my english
But I don't need to use time interval. I want to use distance interval.
NSTimer doesn't know anything about distances. NSTimer does one thing: it sends a message to an object after some time period has elapsed. You supply the message, the object, and the time period. You also have the option to make it repeat or not. That's all it does -- it doesn't monitor object positions or anything like that.
You'll need to keep track of the positions of the objects in question yourself. You could build that functionality into the class you're using for your nodes, or you could create a new class that's patterned after NSTimer, but you can't use NSTimer for this task.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I have set a NSTimer and have tried invalidating it but the timer still triggers the selector. I have already tried what many threads have suggested and set and invalidated the timer on the main thread.
Solved the issue by invalidating the timer before re instantiating it.
Before:
self.timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: #selector(self.hide), userInfo: nil, repeats: false)
After:
self.timer?.invalidate()
self.timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: #selector(self.hide), userInfo: nil, repeats: false)
I was having this same problem. This was the only thing that worked for me:
Start the timer:
NSTimer.scheduledTimerWithTimeInterval(1.0, target:self, selector:#selector(timerMethod), userInfo:nil, repeats: false)
And then in timerMethod:
func timerMethod() {
// do what you need to do
if (needs to be called again) {
NSTimer.scheduledTimerWithTimeInterval(1.0, target:self, selector:#selector(timerMethod), userInfo:nil, repeats: false)
}
}
This was the only way I got it to work. Taken from here: NSTimer doesn't stop it is Mona's answer.
If you created it on a separate thread it won't invalidate.
Doc for invalidate() says
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.
I'm trying to have a label subtract every second (a countdown of sorts) and when I make a variable "timer" and set it to NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "countdown", userInfo: nil, repeats: true) it won't work. The countdown function that I'm trying to call subtracts from the label but when I try to put the "countdown" in the selector it says to replace it with #selector(Hard.countdown). All the tutorials on YouTube are different so I do not know what to do. Your help will be greatly appreciated.
Swift 2.2 added the new #selector syntax for safer selector. (We use pure string before). For NSTimer, you'll need to have a countdown method like the following:
func countdown(timer: NSTimer)
Don't forget the parameter, otherwise it won't work. And reference it as a selector like this: #selector(Hard.countdown(_:))