I know there are a lot of questions on similar topics, but I don't think any quite address this question (although I'm happy to be proved wrong!).
First some background; I have developed an app that monitors user location in the background and this is working fine, but battery usage is high. To try and improve this, my objective is to 'wake up' every x minutes, call startUpdatingLocation get a position, then call stopUpdatingLocation.
Consider the following sample that I put together for testing:
#interface ViewController ()
#property (strong, nonatomic) NSTimer *backgroundTimer;
#property (strong, nonatomic) CLLocationManager *locationManager;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Add observers to monitor background / foreground transitions
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
_locationManager = [[CLLocationManager alloc] init];
[_locationManager requestAlwaysAuthorization];
_locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
_locationManager.delegate = self;
_locationManager.distanceFilter=100;
[_locationManager startUpdatingLocation];
NSTimeInterval time = 60.0;
_backgroundTimer =[NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(startLocationManager) userInfo:nil repeats:YES];
}
-(void)applicationEnterBackground{
NSLog(#"Entering background");
}
-(void)applicationEnterForeground{
NSLog(#"Entering foreground");
}
-(void)startLocationManager{
NSLog(#"Timer fired, starting location update");
[_locationManager startUpdatingLocation];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
NSLog(#"New location received");
[_locationManager stopUpdatingLocation];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#end
As expected, when the application enters the background the CLLocationManager is not updating location, so the NSTimer is paused until the application enters the foreground.
Some similar SO questions have asked about keeping NSTimers running in the background, and the use of beginBackgroundTaskWithExpirationHandler so I added this to the viewDidLoad method just before the NSTimer is started.
UIBackgroundTaskIdentifier bgTask = 0;
UIApplication *app = [UIApplication sharedApplication];
NSLog(#"Beginning background task");
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Background task expired");
[app endBackgroundTask:bgTask];
}];
NSLog(#"bgTask=%d", bgTask);
Over short tests (whether it works long term is yet to be proved) this seems to address the issue, but I don't understand why.
If you take a look at the log output below, you can see that calling beginBackgroundTaskWithExpirationHandler achieves the desired objective as the NSTimer continues to fire when the app enters the background:
2015-03-26 15:45:38.643 TestBackgroundLocation[1046:1557637] Beginning background task
2015-03-26 15:45:38.645 TestBackgroundLocation[1046:1557637] bgTask=1
2015-03-26 15:45:38.703 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:45:46.459 TestBackgroundLocation[1046:1557637] Entering background
2015-03-26 15:46:38.682 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:46:38.697 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:47:38.666 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:47:38.677 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:48:38.690 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:48:38.705 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:48:42.357 TestBackgroundLocation[1046:1557637] Background task expired
2015-03-26 15:49:38.733 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:49:38.748 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:50:38.721 TestBackgroundLocation[1046:1557637] Timer fired, starting location update
2015-03-26 15:50:38.735 TestBackgroundLocation[1046:1557637] New location received
2015-03-26 15:50:44.361 TestBackgroundLocation[1046:1557637] Entering foreground
From this, you can see that the background task expires after approximately 3 minutes as expected, but the NSTimer continues to fire after that.
Is this behaviour expected? I will run some more tests to see if this solution works in the long term, but I can't help but feel I'm missing something with the use of the background task?
After lots of testing, it turns out that the NSTimer does not run indefinitely after the task has expired. At some point (can be hours, can be immediately) the timer ceases to fire.
In the end, the solution to my problem was reducing the GPS accuracy and increasing the distance filter at times where I did not need to monitor the position, as discussed here:
Periodic iOS background location updates
There is an option to use background task for long running tasks and then iOS doesn't suspednd execution.
According to Background execution document to use it one should add UIBackgroundModes key to Info.plist with value location (for location updates). There could be also another reason:
From section Implementing Long-Running Tasks:
Apps that implement these services must declare the services they support and use system frameworks to implement the relevant aspects of those services. Declaring the services lets the system know which services you use, but in some cases it is the system frameworks that actually prevent your application from being suspended.
Probably it is enough to initialize CLLocationManager to cause background task not to be suspended.
Related
I have gone through so many links, even after that I haven't found a proper solution for getting latitude and longitude.
Periodic iOS background location updates
iOS long-running background timer with "location" background mode
I tried from some links and forums but it is working for only 3 mins, then app is not at all updating the user location.
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
//create new uiBackgroundTask
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
//and create new timer with async call:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[locationManager startUpdatingLocation];
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(startTrackingBg) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
// store data
CLLocation *newLocation = [locations lastObject];
//tell the centralManager that you want to deferred this updatedLocation
if (_isBackgroundMode && !_deferringUpdates)
{
_deferringUpdates = YES;
[locationManager allowDeferredLocationUpdatesUntilTraveled:CLLocationDistanceMax timeout:10];
}
}
Ok.
After struggling for 3days, it is working for me for sending latitude and longitude when app is in background even after 3 mins.
I checked my app, continuously sending lat long for more than a hour in background.
It can help some one at least.
First Please add below two keys in your pList.
1.NSLocationAlwaysUsageDescription
2.NSLocationWhenInUseUsageDescription
Bothe are strings and you can give any value.
Then please turn on background fetch and check location updates under capabilities in project section.
Then import Corelocation framework and add this below code.
locationManager is a global variable.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//create CLLocationManager variable
locationManager = [[CLLocationManager alloc] init];
//set delegate
locationManager.delegate = self;
app = [UIApplication sharedApplication];
// This is the most important property to set for the manager. It ultimately determines how the manager will
// attempt to acquire location and thus, the amount of power that will be consumed.
if ([locationManager respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)]) {
[locationManager setAllowsBackgroundLocationUpdates:YES];
}
locationManager.desiredAccuracy = 45;
locationManager.distanceFilter = 100;
// Once configured, the location manager must be "started".
[locationManager startUpdatingLocation];
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
[locationManager stopUpdatingLocation];
[locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[locationManager setDistanceFilter:kCLDistanceFilterNone];
locationManager.pausesLocationUpdatesAutomatically = NO;
locationManager.activityType = CLActivityTypeAutomotiveNavigation;
[locationManager startUpdatingLocation];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
[locationManager stopUpdatingLocation];
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
bgTask = UIBackgroundTaskInvalid;
}];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(startTrackingBg)
userInfo:nil
repeats:YES];
}
-(void)startTrackingBg {
[locationManager startUpdatingLocation];
NSLog(#"App is running in background");
}
//starts automatically with locationManager
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
latitude=newLocation.coordinate.latitude;
longitude=newLocation.coordinate.longitude;
NSLog(#"Location: %f, %f",newLocation.coordinate.longitude, newLocation.coordinate.latitude);
}
You need to refer this apple documentation handling location events in the background
You need to enable location updates in background modes in capabilities of your Xcode project.
The Standard location service wont work in background mode so you have to use either Significant-change location service or Visits service .
Use this code to
locationManager.delegate = self
locationManager.startMonitoringSignificantLocationChanges()
enable Significant-change location service.
Building on Santos answer which has most of the important steps to make background positioning work, I have clarified and corrected some details.
Project settings
You should add these keys to your Xcode Target Info property list. Make sure to add a valid description to each of them, or your app might fail approval.
NSLocationAlwaysUsageDescription
NSLocationWhenInUseUsageDescription
NSLocationAlwaysAndWhenInUseUsageDescription
Next, in Target Capabilities, Turn on Background Modes and check Location updates and Background fetch. Location updates will enable locations in the background and background fetch will allow you to use the network in the background.
Start monitoring
Start by creating an instance of CLLocationManager, configure it and turn on background updates, then start it. Although the code below uses the most common function startUpdatingLocation to get locations, there are several other services to use. It is important to choose the most suitable service as this impacts greatly on battery usage and if the app will be re-launched or not by iOS. See below for more info on this.
// Declare as properties in your class
#property (strong, nonatomic) CLLocationManager *locationManager;
#property (strong, nonatomic) CLLocation *lastLocation;
// Call to start
- (void)initializeAndStartLocationService {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
// Must be set for background operation
self.locationManager.allowsBackgroundLocationUpdates = YES;
// Optional configuration
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.pausesLocationUpdatesAutomatically = YES;
self.locationManager.showsBackgroundLocationIndicator = YES;
// Authorize - the lazy way
[self.locationManager requestAlwaysAuthorization];
// Start the standard location service, there are others
[self.locationManager startUpdatingLocation];
}
// Delegate method that will receive location updates
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
// Keep the last received location
self.lastLocation = [locations lastObject];
NSLog(#"New position %f, %f", self.lastLocation.coordinate.latitude, self.lastLocation.coordinate.longitude);
// Do something with the location or retrieve the location later
// :
}
Stop monitoring
Don't forget to stop monitoring to conserve battery. You can start and stop several times on a single instance of CLLocationManager.
- (void)dealloc {
[self.locationManager stopUpdatingLocation];
}
Automatic app re-launch considerations
When your app is running in the background it can (and actually frequently will after some time) be terminated by iOS. Depending on the type of location updates you are using, iOS will or will not re-launch your app automatically for you. In cases where iOS do not re-launch, the user must start your app again to continue background processing.
Read more on this page.
Read more
CLLocationManager - Core Location | Apple Documentation
Handling Location Events in the Background | Apple Documentation
In my app I need to perform location updates in background. For this purpose I registered my location tracking object as observer like this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(start)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
and this is the method that starts updating location:
- (void)start
{
NSInteger downloadsCount = [[SGDataManager sharedInstance] countOfActiveDownloads];
NSInteger uploadsCount = [[SGDataManager sharedInstance] countOfActiveUploads];
if (downloadsCount + uploadsCount > 0)
{
[self.locationManager startUpdatingLocation];
}
}
However location updates never start. But if I change UIApplicationDidEnterBackgroundNotification to UIApplicationWillResignActiveNotification then the location updates work perfectly in background. So how do I make it work for did enter background notification?
I want to note that location updates background mode is enabled for my app
When your application goes in to background it has finite time to finish tasks. After that time the tasks will be suspended.
Try to put your UpdateLocation into a background task, like here:
http://hayageek.com/ios-long-running-background-task/
This example also uses location updates while going into background.
EDIT:
In my opinion it's a CoreLocation bug. It could be something like: startUpdatingLocation method finishes, the background task finishes, but there is still something happening in some other thread that CoreLocation spawned and the app suspends this thread because it goes into background. It's just a guess though.
Either way, here's a workaround: extend your app lifetime in background. Do not end the background task when startUpdatingLocation finishes, let it run for a couple of seconds.
__block UIBackgroundTaskIdentifier back = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask: back];
back = UIBackgroundTaskInvalid;
}];
[manager startUpdatingLocation];
This code will let your app run in background for a couple of minutes. You can create a timer that will suspend it faster if you like
NSTimer* killBackgroundTaskTimer = [NSTimer scheduledTimerWithTimeInterval: 10 target: self
selector: #selector(killBackgroundTask:) userInfo: nil repeats: NO];
-(void) callAfterSixtySecond:(NSTimer*) t
{
[application endBackgroundTask: back];
back = UIBackgroundTaskInvalid;
}
You need to enable project Background Modes capabilities.
Go to project properties >> Capabilities section.
Select Background Modes and click switch to turn ON.
In background mode check Location updates true.
This will enable location updates in background mode.
I've seen hundreds of solutions of how to get a NSTimer to run in the background.
I know that it is possible, just look at apps like Strava and Runkepper that tracks your time when working out.
But what is the best practice solution for doing so? I can't find one unison solution for this.
Also, I would like the NSTimer to be used across different UIViewControllers. How is this done as a best practice?
Thanks in regards! :)
NSTimers don't run in the background. Store the current time and the elapsed time of the timer when you got the background. When you come back to the foreground, you set up a new timer, using those two pieces of information to setup any state or data that needs to reflect the total elapsed time.
To share between viewCOntroller, just have one object implement this timer, and expose a property on it (e.g. elapsedTime) that gets updated every time interval . Then you can have the viewCOntrollers (that have a reference to that object) observe that property for changes.
You Can Try This Code in Your application NSTimers don't run in the background. acceding to apple But We Try forcefully Only 3 mint
AppDelegate.h
#property (nonatomic, unsafe_unretained) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
#property (nonatomic, strong) NSTimer *myTimer;
- (BOOL) isMultitaskingSupported;
- (void) timerMethod:(NSTimer *)paramSender;
AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
if ([self isMultitaskingSupported] == NO)
{
return;
}
self.myTimer =[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(timerMethod:) userInfo:nil
repeats:YES];
self.backgroundTaskIdentifier =[application beginBackgroundTaskWithExpirationHandler:^(void) {
[self endBackgroundTask];
}];
}
pragma mark - NSTimer Process
- (BOOL) isMultitaskingSupported
{
BOOL result = NO;
if ([[UIDevice currentDevice]
respondsToSelector:#selector(isMultitaskingSupported)]){ result = [[UIDevice currentDevice] isMultitaskingSupported];
}
return result;
}
- (void) timerMethod:(NSTimer *)paramSender{
NSTimeInterval backgroundTimeRemaining =
[[UIApplication sharedApplication] backgroundTimeRemaining];
if (backgroundTimeRemaining == DBL_MAX)
{
NSLog(#"Background Time Remaining = Undetermined");
}
else
{
NSLog(#"Background Time Remaining = %.02f Seconds",backgroundTimeRemaining);
}
}
- (void) endBackgroundTask
{
dispatch_queue_t mainQueue = dispatch_get_main_queue(); __weak AppDelegate *weakSelf = self;
dispatch_async(mainQueue, ^(void) { AppDelegate *strongSelf = weakSelf; if (strongSelf != nil){
[strongSelf.myTimer invalidate];
[[UIApplication sharedApplication]
endBackgroundTask:self.backgroundTaskIdentifier];
strongSelf.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
} });
}
As pointed out in the comments, NSTimer won't work in the background, backround execution on iOS is quite tricky and only works in certain cases, check the Apple Docs on the topic, also this is an excellent read to acquire more background knowledge.
As for your case, it sound like you want to use UILocalNotification. As I understand from your comment:
I want to have a timer running while the app is not in the foreground. Just like Apples own timer app.
Apple's timer app uses UILocalNotification. It gives you a way to schedule a notification which will appear at a certain point in time to the user, regardless of whether the app is in the foreground or background! All you have to do in your app is schedule a notification, e.g. like this:
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = dateTime;
localNotification.alertBody = [NSString stringWithFormat:#"Alert Fired at %#", dateTime];
localNotification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
Then iOS will handle the rest for you :)
I am having difficulties getting this to work for when the app is not running. I have locationManager:didRangeBeacons:inRegion: implemented and it is called when the app is running in the foreground or background, however it doesn't seem to do anything when I quit the app and lock the screen. The location services icon goes away and I never know that I entered a beacon range. Should the LocalNotification still work?
I have Location updates and Uses Bluetooth LE accessories selected in Background Modes (XCode 5) I didn't think I needed them.
Any help greatly appreciated.
-(void)watchForEvents { // this is called from application:didFinishLaunchingWithOptions
id class = NSClassFromString(#"CLBeaconRegion");
if (!class) {
return;
}
CLBeaconRegion * rflBeacon = [[CLBeaconRegion alloc] initWithProximityUUID:kBeaconUUID identifier:kBeaconString];
rflBeacon.notifyOnEntry = YES;
rflBeacon.notifyOnExit = NO;
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startRangingBeaconsInRegion:rflBeacon];
[self.locationManager startMonitoringForRegion:rflBeacon];
}
-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
if (beacons.count == 0 || eventRanged) { // breakpoint set here for testing
return;
}
eventRanged = YES;
if (backgroundMode) { // this is set in the EnterBackground/Foreground delegate calls
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = [NSString stringWithFormat:#"Welcome to the %# event.",region.identifier];
notification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
// normal processing here...
}
Monitoring can launch an app that isn't running. Ranging cannot.
The key to having monitoring launch your app is to set this poorly documented flag on your CLBeaconRegion: region.notifyEntryStateOnDisplay = YES;
This can launch your app on a region transition even after completely rebooting your phone. But there are a couple of caveats:
Your app launches into the background only for a few seconds. (Try adding NSLog statements to applicationDidEnterBackground and other methods in your AppDelegate to see what is going on.)
iOS can take its own sweet time to decide you entered a CLBeaconRegion. I have seen it take up to four minutes.
As far as ranging goes, even though you can't have ranging wake up your app, you can make your app do both monitoring and ranging simultaneously. If monitoring wakes up your app and puts it into the background for a few seconds, ranging callbacks start up immediately. This gives you a chance to do any quick ranging actions while your app is still running.
EDIT: Further investigation proves that notifyEntryStateOnDisplay has no effect on background monitoring, so the above should work regardless of whether you have this flag. See this detailed explanation and discussion of delays you may experience
Code for iOS 9 to range beacons in the background, by using Location Updates:
Open Project Settings -> Capabilities -> Background Modes -> Toggle Location Updates and Uses Bluetooth LE accessories to ON.
Create a CLLocationManager, request Always monitoring authorization (don't forget to add the Application does not run in background to NO and NSLocationAlwaysUsageDescription in the app's info.plist) and set the following properties:
locationManager!.delegate = self
locationManager!.pausesLocationUpdatesAutomatically = false
locationManager!.allowsBackgroundLocationUpdates = true
Start ranging for beacons and monitoring region:
locationManager!.startMonitoringForRegion(yourBeaconRegion)
locationManager!.startRangingBeaconsInRegion(yourBeaconRegion)
locationManager!.startUpdatingLocation()
// Optionally for notifications
UIApplication.sharedApplication().registerUserNotificationSettings(
UIUserNotificationSettings(forTypes: .Alert, categories: nil))
Implement the CLLocationManagerDelegate and in your didEnterRegion send both startRangingBeaconsInRegion() and startUpdatingLocation() messages (optionally send the notification as well) and set the stopRangingBeaconsInRegion() and stopUpdatingLocation() in didExitRegion
Be aware that this solution works but it is not recommended by Apple due to battery consumption and customer privacy!
More here: https://community.estimote.com/hc/en-us/articles/203914068-Is-it-possible-to-use-beacon-ranging-in-the-background-
Here is the process you need to follow to range in background:
For any CLBeaconRegion always keep monitoring on, in background or foreground and keep notifyEntryStateOnDisplay = YES
notifyEntryStateOnDisplay calls locationManager:didDetermineState:forRegion: in background, so implement this delegate call...
...like this:
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{
if (state == CLRegionStateInside) {
//Start Ranging
[manager startRangingBeaconsInRegion:region];
}
else{
//Stop Ranging
[manager stopRangingBeaconsInRegion:region];
}
}
I hope this helps.
You are doing two separate operations here - 'ranging' beacons and monitoring for a region. You can monitor for a region in the background, but not range beacons.
Therefore, your implementation of locationManager:didRangeBeacons:inRegion: won't get called in the background. Instead, your call to startMonitoringForRegion will result in one / some of the following methods being called:
– locationManager:didEnterRegion:
– locationManager:didExitRegion:
– locationManager:didDetermineState:forRegion:
These will get called in the background. You can at that point trigger a local notification, as in your original code.
Your app should currently wake up if you're just wanting to be notified when you enter a beacon region. The only background restriction I know of concerns actually hosting an iBeacon on an iOS device. In that case, the app would need to be physically open in the foreground. For that situation, you'd be better off just doing the straight CoreBluetooth CBPeripheralManager implementation. That way you'd have some advertising abilities in the background.
I'm currently writing an application which depends on location tracking and sending data about the position to the server. The problem, however, is that it has to run 24/7 and currently I'm experiencing random crashes which occur every 2-3 days. What I have done to make the application run constantly in the background is I put a NSTimer in a beginBackgroundTaskWithExpirationHandler method right iside the applicationDidEnterBackground method. The timer executes each minute and stops/starts the location service.
Here is a sample crash log
The code basically looks like this:
UIApplication *app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTaskId = 0;
bgTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval: 1 * 60.0 target: self selector: #selector(onTick) userInfo: nil repeats: YES];
[t fire];
if (bgTaskId != UIBackgroundTaskInvalid){
[app endBackgroundTask: bgTaskId];
bgTaskId = UIBackgroundTaskInvalid;
}
}];
I am using GCDAsyncSockets for connection purposes, each call having a timeout of approximately 30 seconds.
I'm really out of ideas, what might be the reason the crashes occur?
Your timer is probably firing off AFTER the task is invalidated (after [UIApplication sharedApplication].backgroundTimeRemaining gets to 0.
The thing is that you can't make the application run constantly in the background. If you want to execute code every once in a while, your only option is going to be using the background location API, setting that your app is using the location background mode in its plist.
You would be getting the CLLocationManagerDelegate callbacks, and you have some time to do some work when those methods are called.
See the Apple documentation regarding background modes: http://developer.apple.com/library/ios/#DOCUMENTATION/iPhone/Conceptual/iPhoneOSProgrammingGuide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html
And the location-awarness manual: http://developer.apple.com/library/ios/#DOCUMENTATION/UserExperience/Conceptual/LocationAwarenessPG/Introduction/Introduction.html#//apple_ref/doc/uid/TP40009497