NSTimer not firing after home button is pressed - ios

II have an NSTimer set to fire each second. Using the Simulator this will call the eachSecond() method correctly after I navigate away from the app by pressing the home button. However, on a real iPhone 6, once I navigate away eachSeconds is not called again until the app is open again.
Why might this behave differently on a real device? Is there anyway to ensure it will fire each second in this use case?
Its a fitness app, so needs to record duration when the phone locks or user navigates away momentarily.
Thanks,
lazy var timer = NSTimer()
fun init() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(RunViewController.eachSecond(_:)), userInfo: nil, repeats: true)
}
func eachSecond() {
// update UI etc.
}

An NSTimer will not fire when your app is suspended. Background execution is different when running under XCode; background execution limits do not apply.
In order to determine how much time has elapsed while your app is suspended, you can store a timestamp in applicationDidEnterBackground and calculate the elapsed time based on the difference between the saved timestamp and current time in applicationWillEnterForeground

You need to add the timer to the main run loop.
func init() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(RunViewController.eachSecond(_:)), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
}

Related

Swift 3 precise Timer repeating (iOS 9)

I've got a precise timer that needs to be update every 40ms (most precisely than possible). On iOS10 it's good (I use the new scheduleRepeating method) but on iOS9 I need to use the old way (scheduledTimer) and it's pretty laggy (sometimes 24ms, sometimes its 72...), so my hardware interface and visual effects and lagging.
Any suggestion?
func launchTimer() {
if #available(iOS 10.0, *) {
startTickTimer()
} else {
let timerQueue = DispatchQueue(label: "my.queue.tickTimer", attributes: .concurrent)
self.swiftTimer = Timer.scheduledTimer(timeInterval: period, target: self, selector: #selector(executeTimer), userInfo: nil, repeats: true)
timerQueue.async {
RunLoop.current.add(self.swiftTimer!, forMode: RunLoopMode.defaultRunLoopMode)
}
}
}
static func startTickTimer() {
let queue = DispatchQueue(label: "my.queue.tickTimer", attributes: .concurrent)
DMXTimer.tickTimer?.cancel()
DMXTimer.tickTimer = DispatchSource.makeTimerSource(queue: queue)
DMXTimer.tickTimer?.scheduleRepeating(deadline: .now(), interval: .milliseconds(40), leeway: .seconds(1))
DMXTimer.tickTimer?.setEventHandler {
executeTimer()
}
DMXTimer.tickTimer?.resume()
}
static func executeTimer() {
print("hello moto")
}
From Apple doc about NSTimer:
A timer is not a real-time mechanism. If a timer’s firing time occurs
during a long run loop callout or while the run loop is in a mode that
isn't monitoring the timer, the timer doesn't fire until the next time
the run loop checks the timer. Therefore, the actual time at which a
timer fires can be significantly later. See also Timer Tolerance.
If you want a very precise timer you can check an implementation from Apple Tech Note and adapt in Swift or use a CADisplayLink for display updates.
I would suggest to use NSThread and loop inside instead of timer. It is more complicated but, imho, it has more settings and flexible behaviour

Intermittent Missing Seconds with Timer

I have a timer, which is a singleton, that repeatedly fires every second. I allow the user to pause the timer as well as resume. I am keeping track of the start date of the timer, and I am subtracting any pauses from the elapsed time.
Unfortunately, I can't seem to fix an intermittent issue where pausing and resuming the timer causes a skipping of one second.
For instance, in the following code block, I start the timer and print the seconds:
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0
10.0
11.0
12.0
13.0
14.0
15.0
16.0
17.0
18.0
19.0
In the following code block, I resume the timer, printing the seconds again. However, as you can see, the 20th second has not been printed:
21.0
22.0
23.0
24.0
25.0
26.0
I cannot seem to figure out where I am losing the second. It does not happen with each pause and resume cycle.
The properties that I am using to keep track of the aforementioned are as follows:
/// The start date of the timer.
private var startDate = Date()
/// The pause date of the timer.
private var pauseDate = Date()
/// The number of paused seconds.
private var paused = TimeInterval()
/// The number of seconds that have elapsed since the initial fire.
private var elapsed = TimeInterval()
I start the timer by creating the timer and setting the start date:
/// Starts the shower timer.
func startTimer() {
// Fire the timer every second.
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
// Set the start time of the initial fire.
startDate = Date()
}
If the user pauses the timer, then the following method executes:
/// Pauses the shower timer.
func pauseTimer() {
// Pause the timer.
timer?.invalidate()
// Set the timer to `nil`, according to the documentation.
timer = nil
// Set the date of the pause.
pauseDate = Date()
}
Then, the following method executes when the user resumes the timer:
/// Resumes the timer.
func resumeTimer() {
// Recreate the timer.
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
// Add the number of paused seconds to the `paused` property.
paused += Date().timeIntervalSince(pauseDate)
}
The following method, which is called by the method that executes when the timer fires, sets the number of elapsed seconds since the initial fire, less the sum of any pauses:
/// Sets the number of elapsed seconds since the timer has been started, accounting for pauses, if any.
private func updateElapsedTime() {
// Get the date for now.
let now = Date()
// Get the time that has elapsed since the initial fire of the timer, and subtract any pauses.
elapsed = now.timeIntervalSince(startDate).rounded(.down).subtracting(paused.rounded(.down))
}
Finally, the following method is the Selector that executes when the timer fires:
/// Updates the number of elapsed seconds since the timer has been firing.
#objc private func updateElapsedSeconds() {
// Configure the elapsed time with each fire.
updateElapsedTime()
// Post a notification when the timer fires, passing a dictionary that includes the number of elapsed seconds.
NotificationCenter.default.post(name: CustomNotification.showerTimerFiredNotification, object: nil, userInfo: nil)
}
What am I doing incorrectly to cause a missing second intermittently?
So the issue here is that Timer is not accurate in this way. Or rather, its timekeeping is reasonably accurate, but the actual rate of firing has some variance as it is dependent on the runloop.
From the documentation:
A timer is not a real-time mechanism; it fires only when one of the
run loop modes to which the timer has been added is running and able
to check if the timer’s firing time has passed.
To show this, I got rid of all of the rounding in your code and printed the output (you don't even need to pause to see this happen). Here is what this variance looked:
18.0004420280457
19.0005180239677
20.0004770159721
21.0005570054054
21.9997390508652
23.0003360509872
24.0003190040588
24.9993720054626
25.9991790056229
Sometimes it fires particularly late and this causes the whole thing to get thrown off. The rounding doesn't help because you are still depending on the timer for the actual reference time and eventually it will be off by more than a second.
There are a few ways to fix the situation here depending on what exactly you are trying to accomplish. If you absolutely need the actual time, you can adjust the timer to fire at fractions of a second and instead use that output to estimate the seconds a little more accurately. This is more work and will still not be totally right (there will always be a variance).
Based on your code, it seems like simply incrementing a number based on the timer should be enough to accomplish your goal. Here is a simple modification to your code making this work. This will count up simply and never skip a second in the count whether you pause or not:
/// The number of seconds that have elapsed since the initial fire.
private var elapsed = 0
private var timer: Timer?
/// Starts the shower timer.
func startTimer() {
elapsed = 0
// Fire the timer every second.
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
}
/// Pauses the shower timer.
func pauseTimer() {
// Pause the timer.
timer?.invalidate()
// Set the timer to `nil`, according to the documentation.
timer = nil
}
/// Resumes the timer.
func resumeTimer() {
// Recreate the timer.
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
}
/// Sets the number of elapsed seconds since the timer has been started, accounting for pauses, if any.
private func updateElapsedTime() {
// Get the time that has elapsed since the initial fire of the timer, and subtract any pauses.
elapsed += 1
// debug print
print(elapsed)
}
/// Updates the number of elapsed seconds since the timer has been firing.
#objc private func updateElapsedSeconds() {
// Configure the elapsed time with each fire.
updateElapsedTime()
// Post a notification when the timer fires, passing a dictionary that includes the number of elapsed seconds.
NotificationCenter.default.post(name: CustomNotification.showerTimerFiredNotification, object: nil, userInfo: nil)
}

Is it safe to schedule NSTimer on Main thread and keep running the timer when the app is in background?

I have scheduled and NSTimer on main thread.
func startTimer(){
MYClass.runUIBlock { () -> Void in
self.invalidateRecordTimer()
self.updateTimerForInactiveRecord(self.fiveMinutesInSeconds)
}
}
private func updateTimerForInactiveRecord(timeToRecord:Double){
recordTimer = NSTimer(timeInterval: timeToRecord, target: self, selector:"collectData", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(recordTimer!, forMode: NSRunLoopCommonModes)
}
The timer keeps on running even if I am in background. Wanted to ask, is it safe to keep the timer ON without invalidating it before the background timer gets over ? Or do I need to check for available background time and invalidate the timer before background timer gets over ?
At present, the timer keeps on running indefinitely in the background.
An NSTimer can run only a max of 10 minutes when in background. Apple has done this purposefully to avoid battery drainage. If you are not supposed to do important activity when in background then as a good citizen it is advised to stop the timer when going in background.

Cocoa - Schedule timed tasks to fetch data

recently I am writing an OS X menu bar app that display the current weather info.
I am requesting the data using an external weather API, and suppose I can call a getData() method in my AppDelegate to make a HTTP request and get the data back.
Currently I am using NSTimer but to make the request every 15min in the AppDelegate.swift
NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "getData:", userInfo: nil, repeats: true)
However, now what I want to achieve is to execute this method at a specific time. For example, on an hourly basis everyday - get data on 10:00, 11:00, 12:00, etc...
Can anyone suggest on how to achieve this? Thanks!
This is how you can do this:
var fireDate = NSDate()
var timer = NSTimer(fireDate: fireDate, interval: 60.0, target: self, selector: "getData:", userInfo: nil, repeats: true)
timer.fire()

In Swift, how to ivalidate NSTimer in AppDelegate when application going background?

I need to translate my iOS application from obj-c to swift. I have a NStimer in ViewController that loads metadata from shoutcast every 30 seconds, but when application resign active it stops, when enter foreground it runs again.
Edit: OK. Problem solved! I added two observers in viewDidLoad with name UIApplicationWillResignActiveNotification and UIApplicationWillEnterForegroundNotification, like below:
override func viewDidLoad() {
NSLog("System Version is \(UIDevice.currentDevice().systemVersion)");
super.viewDidLoad()
self.runTimer()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "invalidateTimer", name: UIApplicationWillResignActiveNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "runTimer", name: UIApplicationWillEnterForegroundNotification, object: nil)
}
and I made two functions. First one for run timer:
func runTimer(){
loadMetadata()
myTimer.invalidate()
NSLog("timer run");
myTimer = NSTimer.scheduledTimerWithTimeInterval(30.0, target: self, selector: "loadMetadata", userInfo: nil, repeats: true)
let mainLoop = NSRunLoop.mainRunLoop()
mainLoop.addTimer(myTimer, forMode: NSDefaultRunLoopMode)
}
and second to stop it:
func invalidateTimer(){
myTimer.invalidate()
NSLog("timer invalidated %u", myTimer);
}
I hope this can help someone. :)
I suggest you use the appropriate system for your task: https://developer.apple.com/library/ios/documentation/iphone/conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html#//apple_ref/doc/uid/TP40007072-CH4-SW56
Apps that need to check for new content periodically can ask the
system to wake them up so that they can initiate a fetch operation for
that content. To support this mode, enable the Background fetch option
from the Background modes section of the Capabilities tab in your
Xcode project. (You can also enable this support by including the
UIBackgroundModes key with the fetch value in your app’s Info.plist
file.)...
When a good opportunity arises, the system wakes or launches your app
into the background and calls the app delegate’s
application:performFetchWithCompletionHandler: method. Use that method
to check for new content and initiate a download operation if content
is available.

Resources