Is there any way to run NSTimer for more than 3 mins in background?
I have to create simple app that uses
`locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion)`
for scan my beacons. I have run into problem when I needed to check if user is exactly 10 seconds near closest beacon. I created
var timer: NSTimer?
var lastClosestBeacon: CLBeacon? {
didSet {
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: "showLocalNotification", userInfo: nil, repeats: false)
// NSRunLoop.mainRunLoop().addTimer(timer!, forMode: NSDefaultRunLoopMode)
}
}
When closest beacon changes lastClosestBeacon is set and timer is scheduled. It is working in App and for 3 mins when user enters background(locks phone etc.) with help from Scheduled NSTimer when app is in background?
Is there any possibility to check that for more than 3 mins ?
PS. I have already added Background Modes with location updates.
You can do it by following way:
1) First include required background mode keys into your Info.plist
2) Check and add following line of code for adding background working of location manager in iOS 9 (update: also works in iOS 10):
if ([self.locationManager respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)]) {
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
[self.locationManager pausesLocationUpdatesAutomatically:NO];
}
3)Then create a new timer with repeated continuously with every 1 sec.
4)In that timer method add these two line of code.
[self.locationManager stopUpdatingLocation];
[self.locationManager startUpdatingLocation];
This make your app run in background more than 3 mins. To be aware, the battery usage may be costly.
As of iOS 9, apps are allowed a maximum of 180 seconds (3 minutes) of background execution time upon request. This can be extended indefinitely if you put location updates in the “Required background modes” in your Info.plist. I have verified that doing so lets the background execution run forever. Having this in your Info.plist, however, will require you to get approval from Apple for this background mode before putting your app in the App Store.
If you don't want to request location background mode, there are some other tricks you can do to get around Apple's restrictions, which you can read about here: https://gooddevbaddev.wordpress.com/2013/10/22/ios-7-running-location-based-apps-in-the-background/
A word of caution about using those tricks, though -- they are subject to change in any iOS upgrade, and using them might get your app rejected by reviewers.
Related
I have an iOS app which uses CoreLocation and WiFi SSID information. My app was working fine until iOS 13 arrived but since then it's giving me a lot of issues especially when app goes in background. I have been using one timer in background as background task which also not work after 30 seconds and especially app got killed in background in the same time frame. I have seen some people saying that iOS 13 has been strict for background task and timing but I still have not found any direct references or links by apple which supports this claims. Is there anyone else facing the same issues then please share your insights. Thanks
I have one background task for timer:
var bgTask: UIBackgroundTaskIdentifier?
var updateTimer: Timer?
func applicationDidEnterBackground(_ application: UIApplication) {
bgTask = application.beginBackgroundTask(withName: "MyTask", expirationHandler: {() -> Void in
if let bgTask = self.bgTask {
application.endBackgroundTask(bgTask)
self.bgTask = UIBackgroundTaskIdentifier(rawValue: convertFromUIBackgroundTaskIdentifier(UIBackgroundTaskIdentifier.invalid))
}
})
DispatchQueue.main.async(execute: {() -> Void in
self.updateTimer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(self.checkNetworkStatus), userInfo: nil, repeats: true)
})
}
#objc func checkNetworkStatus() {
print("Timer calledddd")
}
The name background task is somewhat misappropriate.
Specifically, beginBackgroundTask(expirationHandler:) doesn’t actually start any sort of background task, but rather it tells the system that you have started some ongoing work that you want to continue even if your app is in the background.
You still have to write the code to create and manage that work.
So it’s best to think of the background task API as raising a “don’t suspend me” assertion.
iOS 13 puts strict limits on the total amount of time that you can prevent suspension using background tasks.
iOS 13 has reduced the from-the-foreground value to 30 seconds.
a. 3 minutes, when your app has moved from the foreground to the background
b. 30 seconds, when your app was resumed in the background
You can get a rough estimate of the amount of time available to you by looking at UIApplication() backgroundTimeRemaining property.
Further info here and here.
You may want to consider a different API to achieve your goals.
I'm making a ibeacon region monitoring app with location updates when the user enter into this region (app not in foreground). This location updates must be configured as kCLLocationAccuracyBestForNavigation accuracy (because I have to make a tracking while the use remain in the region,
subscribe to me significant changes is not enough). Everything works well, but after 20 seconds (sometimes 1 minute o more) I stop receiving locations updates. I put all the keys in info.plist for always location usage, I include the background modes in capabilities section and locations updates on background.
I configure the locationManager with different configurations and always the SO stops my locations updates. I'm using IOS 12 and Iphone 7 for testing.
The way I configure CLLocationManager:
self.locationManager.desiredAccuracy
=kCLLocationAccuracyBestForNavigation
self.locationManager.activityType = .automotiveNavigation
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.delegate = self
self.locationManager.allowsBackgroundLocationUpdates = true
self.locationManager.pausesLocationUpdatesAutomatically = false
Start location updates (when user enter in Ibeacon Region):
func beaconManager(_ manager: KTKBeaconManager, didEnter region:
KTKBeaconRegion) {
self.locationManager.startUpdatingLocation()
}
And finally, in didUpdate locations i call a web service:
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
//Call web service using alamofire
}
I ask for your help to know if I am performing the settings correctly for the purpose I want to perform and any clue that lets me know why the operating system kills my process to get locations updates
Getting regular location updates in the background on iOS is tricky. The operating system is designed to keep apps from constantly running in the background to optimize battery usage, and it suspends them after a period of time unless you have several things exactly right.
You need to do three things:
You must get obtain always location permission from the user (as you say you've done).
You must add the following entry to your Info.plist. This will allow your app to run indefinitely in the background, however if you wish to submit your app to the App Store, this entry will also declare to reviewers that it is a background location app, and you will need to convince them that it provides a location-based benefit to the user, and that the user is aware of this benefit.
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
You must maintain a background thread to keep your app alive. It doesn't matter if you actually do anything in this background thread. Just having it be active keeps iOS from suspending your app.
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid
func extendBackgroundRunningTime() {
if (backgroundTask != UIBackgroundTaskIdentifier.invalid) {
// already started
return
}
NSLog("Attempting to extend background running time")
self.backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "DummyTask", expirationHandler: {
NSLog("Background task expired by iOS. Cannot run again until a new beacon region event")
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskIdentifier.invalid
})
DispatchQueue.global().async {
while (true) {
let backgroundTimeRemaining = UIApplication.shared.backgroundTimeRemaining
// This will be a very large number if you have proper permissions
// If not, it will generally count down from 10 seconds once you are in the
// background until iOS suspends your app.
NSLog("Thread background time remaining: \(backgroundTimeRemaining)")
Thread.sleep(forTimeInterval: 1.0)
}
}
}
I am experiencing a problem in a tracking app, but only on iOS 11. The app passively records your GPS position in the background under certain conditions.
The problem that occurs on iOS 11 is that seemingly randomly CLLocationManager stops reporting GPS events for anywhere from 10 to 900+ seconds.
The location manager is set up like the following:
let locationManager = CLLocationManager()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.startMonitoringSignificantLocationChanges()
locationManager.desiredAccuracy = 10
locationManager.activityType = .automotiveNavigation
Thinking that the thread CoreLocation is managing and using for all callbacks could be overburden.
I have tried delegating to a different thread for processing, so the app does not tie up CoreLocations resources. This is done using an operation queue that is set up like the following:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.qualityOfService = .userInitiated
with the callback using that operation queue:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
queue.addOperation {
// process locations
}
}
Introducing the operation queue did not help avoid the gaps, but it did make it so that when the gap occurs the location manager reports a bunch of (different) locations with the same timestamp.
The locations with the same timestamps is not all the missing locations, i.e. if there is a gap of 200 seconds, I might only get 15 locations with the same timestamp.
I am hoping someone here can tell me why this is happening and what I can do to avoid these gaps.
Thanks in advance.
After much review, trials, discussion with Apple we now seem have resolve the issue. Even though Apple will only guarantee GPS tracking the background if the app is in the foreground when the tracking starts. The fix we have applied is changing the following:
locationManager.startMonitoringSignificantLocationChanges()
... to:
locationManager.stopMonitoringSignificantLocationChanges()
locationManager.startMonitoringSignificantLocationChanges()
The theory is that the settings for the app become corrupted when applying true to the "monitoring" setting during startup. If first you set the "monitoring" setting to false and afterwards to true, no corruption occurs.
This fix is live in the app store app for several hundreds of people and tracking in the background is working fine.
didRangeBeacons method is almost getting called 40 times every second in iOS 9. However, in iOS 8 and below it was getting called only once every second. Because of this our code logic in didRangeBeacons is not working as expected.
Steps to Reproduce:
To any of your ViewController or AppDelegate, add the CLLocationManager delegates.
Initialise the location manager.
Start ranging -
[self.locationManager startRangingBeaconsInRegion:savedRegion];
Just see how frequently didRangeBeacons is getting called.
Expected Results:
Should get called once every second.
Actual Results:
Is getting called more than 40 times every second
Version:
iOS 9 and above
Make sure you don't change any of the CLLocationManager properties in the didRangeBeacons method. This will cause the CLLocationManager calling the didRangeBeacons again and you will end up with lots of updates in a second.
I've experienced this with 'startUpdatingLocation' but should have the results.
Having heard multiple reports of this, I have tried to reproduce this without success. For a test setup, I created one CLBeaconRegion object and registered it for ranging. I then counted the number of callbacks to the didRangeBeaconsInRegion method, and divided it by the number of seconds since the test started to get a measurement of callbacks per second.
CLBeaconRegion *region = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc]initWithUUIDString:#"2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6"] identifier:#"test"];
[self.rangingLocationManager startRangingBeaconsInRegion: region];
...
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)iBeacons inRegion:(CLBeaconRegion *)region {
if (startTime == nil) {
startTime = [[NSDate alloc] init];
}
long secsSinceStart = -[startTime timeIntervalSinceNow];
callbacks++;
NSLog(#"Ranging callbacks per second: %1.2f", 1.0*callbacks/secsSinceStart);
}
Here are my results from NSLog:
iOS Version: 9.0.2 (13A452)
Phone Model: iPod Touch ME643LL/A
2015-10-09 17:20:02.660 beaconTest[214:7044] Ranging callbacks per second: 1.03
iOS Version: 8.3 (12F69)
Phone Model: iPod touch ME978LL/A
2015-10-09 17:27:35.842 beaconTest[629:242941] Ranging callbacks per second: 1.02
The fact that I cannot reproduce doesn't mean this issue does not exist. It is possible it was an issue with an earlier version of iOS 9 than is currently available. It is possible that it only exists under certain conditions that I cannot test.
However, it is safe to conclude that not all iOS devices with 9.0.2 experience the symptoms described in this question.
I've got a problem with location services. I can't set up a function that updates my location coordinates in the background by a NSTimer. Here is my code from appDelegate:
var locationManager = CLLocationManager()
func applicationDidEnterBackground(application: UIApplication) {
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.theTimer = NSTimer(fireDate: NSDate(), interval: 40, target: self, selector: "handleTimer", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.theTimer, forMode: NSDefaultRunLoopMode)
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var locValue:CLLocationCoordinate2D = manager.location.coordinate
println("dinBack = \(locValue.latitude) \(locValue.longitude)")
self.locationManager.stopUpdatingLocation()
}
func handleTimer(){
println("started")
self.locationManager.startUpdatingLocation()
}
PS. - Of course that i've imported corelocation.
- When I get back into the app, the console prints what should have printed in the background.
You can not make an NSTimer work like this while your application is in the background. NSTimer's are not "real-time mechanisms". From the official documentation:
Timers work in conjunction with run loops. To use a timer effectively, you should be aware of how run loops operate—see NSRunLoop and Threading Programming Guide. Note in particular that run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.
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. Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds. If a timer’s firing time occurs during a long callout or while the run loop is in a mode that is not monitoring the timer, the timer does not fire until the next time the run loop checks the timer. Therefore, the actual time at which the timer fires potentially can be a significant period of time after the scheduled firing time.
Emphasis mine.
The important take away from this is that while your application is in the background, any run loop that your timer would have been scheduled on is not actively running.
As soon as your app returns to the foreground, this run loop fires back up, sees that your timer is overdue, and sends the message to the selector.
With iOS 7 and forward, if you want to perform operations in the background, you can tell the OS that you want to perform "background fetches".
To set this up, we must first tell the OS how frequently we want to fetch data, so in didFinishLaunching..., add the following method:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
return true
}
We can pass any time interval here (for example, if we only want to check once a day). The value we pass in is only defining a minimum amount of time that should pass between checks, however. There is no way to tell the OS a maximum amount of time between checks.
Now, we must implement the method that actually gets called when the OS gives us an opportunity to do background work:
func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// do background work
}
We can do whatever we want within this method. There are two catches, however.
This method is called while our app is in the background. The OS limits us to (I believe) thirty seconds. After thirty seconds, our time is up.
We must call the completionHandler() (or the OS will think we used all of our time).
The completionHandler that gets passed in takes an enum, UIBackgroundFetchResult. We should pass it either .Failed, .NewData, or .NoData, depending upon what our actual results were (this approach is typically used for checking a server for fresh data).
So, our method might look like this:
func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// do stuff
if let _ = error {
completionHandler(.Failed)
} else if results.count > 0 {
completionHandler(.NewData)
} else {
completionHandler(.NoData)
}
}
Keep in mind, we have absolutely zero control over how frequently the OS will actually let us run this code in the background. The OS uses several metrics in order to optimize the user's experience.
I think if your app reports .Failed to the completion handler, the OS might give you a second chance soon, however if you're abusing .Failed, the OS could probably blacklist your application from using background fetches (and Apple could deny your app).
If your app isn't reporting .NewData, the OS will let your app do background work less often. I'm not saying this because I recommend that you just always report .NewData. You should definitely report accurately. The OS is very smart about scheduling work. If you're passing .NewData when there isn't new data, the OS will let your app work more often than it may need to, which will drain the user's battery quicker (and may lead to them uninstalling your app altogether).
There are other metrics involved in when your app gets to do background work however. The OS is very unlikely to let any app do background work while the user is actively using their device, and it is more likely to let apps do background work while the user is not using their device. Additionally, OS is more likely to do background work while it is on WiFi and while it is plugged into a charger of some sort.
The OS will also look at how regularly the user uses your app, or when they regularly use it. If the user uses your app every day at 6pm, and never at any other time, it's most likely that your app will always get an opportunity to do background work between 5:30pm and 6pm (just before the user will use the app) and never during any other part of the day. If the user very rarely uses your app, it may be days, weeks, or months between opportunities to work in the background.