This is the example from developer.apple.com
Finite-Length Tasks
Starting a background task at quit time
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task, preferably in chunks.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
I want to implement the Long-Running Task, not the "Finite-Length" one. I've not found any examples written with objective-c for current version sdk. Can I start it, say, on application start and run it continuously wheter app is in foreground or background? How do i do that?
I'm a react-native developer, and i've just begun learning objective-c. Therefore i may need just simple example to follow. I've already implemented bridge instance to Cocoa Touch Class, it works fine. All i need is to launch the Long-Running Task in this class. I need it for BLE, but for sake of simplicity, i'd say, let's use location tracking, as it's easier and quicker to test.
My LongRunningTask.m:
#import "LongRunningTask.h"
#implementation LongRunningTask
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(start:(RCTResponseSenderBlock)callback)
{
// start the Long-Running Task here
callback(#[#"done"]);
}
I don't get it, how is long-running task being defined? Seems, there is no specific method or any marker, which would declare a task to be long-running. So, technically, if i'm getting permission from user to run a specific type of long-running task, i can continuously run whatever code i want within applicationDidEnterBackground? Even if it has nothing in common with the permission i've got?
And the only factor that affects if this task will be terminated in 10-ish minutes is if i've got the permission or not?
Thank you!
For the term Long-Running Task means the task which is active until the app has been killed, I am giving you simple example of LocationManager
When you setup your app to receive Location Updates and initialize the LocationManager the app is subjected to receive location updates until you stop the updates in foreground, same is the case with BLE.
See the examples,
_locationManager=[[CLLocationManager alloc] init];
_locationManager.delegate=self;
if ([_locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)]) {
[_locationManager requestWhenInUseAuthorization];
[_locationManager requestAlwaysAuthorization];
}
[_locationManager startUpdatingLocation];
The above code starts the LocationManager for the app to receive GPS location updates if the user has given permission to the app to receive GPS location and if the GPS settings for app is ON to receive updates, the method below will get called till your app is in Foreground
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
Same happens with BLE
If you want your app to be able to receive GPS or, BLE updates in Background you need to turn on related Background Modes from the Project Settings of the app as shown below in the image
The image shows the list of services you can run while app in background, apart from the list you can do certain network activities like downloads and uploads which you have shown in your example, which will run a Long-Running Task, until you kill the app, or the service is interrupted from settings by user manually.
Hope above clears your doubt.
Cheers.
Related
I wrote an app the monitors a user's location. Location services are turned on when my view loads as such:
// Create the location manager if this object does not
// already have one.
if (nil == self.locationManager) {
self.locationManager = [[CLLocationManager alloc] init];
}
self.locationManager.delegate = self;
// Check for iOS 8. Without this guard the code will crash with "unknown selector" on iOS 7.
if ([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
[self.locationManager startMonitoringSignificantLocationChanges];
NSLog(#"Started monitoring significant location changes");
If I terminate the app while its active, the location services stop. Here is the code I wrote to stop the location services in the AppDelegate.m:
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also
applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
NSLog(#"entered terminate in delegate");
[myController.locationManager stopUpdatingLocation];
[myController.locationManager stopMonitoringSignificantLocationChanges];
myController.locationManager = nil;
[self saveContext];
}
I ran into a problem such that if my app is already in the background, the above method is not called and as such I could not turn off location services. To work around this, I found this solution which I tried:
- (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.
UIApplication *app = [UIApplication sharedApplication];
if ([app respondsToSelector:#selector(beginBackgroundTaskWithExpirationHandler:)]) {
self.bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
// Synchronize the cleanup call on the main thread in case
// the task actually finishes at around the same time.
dispatch_async(dispatch_get_main_queue(), ^{
if (self.bgTask != UIBackgroundTaskInvalid)
{
NSLog(#"Marking bgTask as Invalid when we entered background");
[app endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
}
});
}];
}
}
So the above solution works if my app was in the background. However, I noticed that if leave my app running in the background for a long time, more than five minutes, the expiration handler kicks in. So then if I terminate the app without bringing it to the foreground. The location services icon still appears on the phone. I have to restart the app or bring it to the foreground first and then terminate it for the code that disables location services kicks in.
If I remove these two lines:
[app endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
Then stopping location services works find after five minutes while the debugger is attached. If I leave it running longer in the background then the terminate code never kicks in. Is it because I am not changing locations or does the app eventually die?
So my question is, is there another way to make sure that the app properly stops location service monitoring if its been in the background for a while?
Thank you...Amro
Edit, I did more experiments and here are my findings:
While attached to the debugger if I wait 11 minutes from time it enters background mode, the method willTerminate gets called:
2015-01-13 08:52:45.935 [441:37074] ###AMRO--->applicationWillResignActive entered
2015-01-13 08:52:46.642 [441:37074] ###AMRO--->Entered background mode
2015-01-13 08:55:42.697 [441:37074] ###AMRO--->beginBackgroundTaskWithExpirationHandler called
2015-01-13 09:03:26.265 [441:37074] entered terminate in delegate
If I try this without debugger, and only wait four minutes, the terminate function does not get called, I don't have to wait the whole 11 minutes.
From the Apple's documentation:
App Termination
Apps must be prepared for termination to happen at any time and should
not wait to save user data or perform other critical tasks.
System-initiated termination is a normal part of an app’s life cycle.
The system usually terminates apps so that it can reclaim memory and
make room for other apps being launched by the user, but the system
may also terminate apps that are misbehaving or not responding to
events in a timely manner.
Suspended apps receive no notification when they are terminated; the
system kills the process and reclaims the corresponding memory. If an
app is currently running in the background and not suspended, the
system calls the applicationWillTerminate: of its app delegate prior
to termination. The system does not call this method when the device
reboots.
In addition to the system terminating your app, the user can terminate
your app explicitly using the multitasking UI. User-initiated
termination has the same effect as terminating a suspended app. The
app’s process is killed and no notification is sent to the app.
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html#//apple_ref/doc/uid/TP40007072-CH2-SW1
About significantChangesLocation:
If you leave the significant-change location service running and your
iOS app is subsequently suspended or terminated, the service
automatically wakes up your app when new location data arrives.
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html
If you don't want your app waked up by significantChangeLocation, you could call stopMonitoringSignificantLocationChanges when backgroundTimeRemaining is about to expire.
Att.
There are several thread on this topic and nothing which works for me. I don't have to deploy the app in the app store, so I can do hacks in the app to keep it running. Any way I can keep the app going even when it is backgrounded? Any pointers appreciated.
I found this github project solving this problem: https://github.com/voyage11/Location
Obviously Apple wouldn't approve this hack, but I guess it would work in your case.
Basically what you have to do is:
Use the location background mode capability in info.plist
Always have background task running, but don't let it run for longer than a minute. Create a new background task every minute and stop old task.
Apart from the previous task rolling, also keep a long running background task. I'm not sure though if that is really needed.
Start the location manager every minute and requestAlwaysAuthorization.
Some important snippets from the referenced code:
Background task:
bgTaskId = [application beginBackgroundTaskWithExpirationHandler:^{
[self.service debugLog:[NSString stringWithFormat:#"BG....background task %lu expired", (unsigned long)bgTaskId]];
}];
And start the location manager:
if(IS_OS_8_OR_LATER) {
[_locationManager requestAlwaysAuthorization];
}
[_locationManager startUpdatingLocation];
I know this is a very common question. I have read many answer but not found out the appropriate answer for me. That's why I post this question and hope someone will show me how to fix my code.
I have function startUpdate to update location using CLLocationManager. In applicationDidEnterBackground method, I write something like below:
[self startUpdate]; // position1
NSLog(#"applicationDidEnterBackground");
__block UIBackgroundTaskIdentifier bgTask;
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
[self startUpdate]; // position2
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self startUpdate]; // position3
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"http://google.com"]];
});
I tried to put [self startUpdate] at one of three positions (position1, 2, 3) and sometime it works, sometime not, and I do not know why.
In case it works, updating just run in 3 minutes event. If I call startUpdate when app is in foreground, then put app to background, updating will last 15 minutes with real device, and more than 1hour with simulator ( I don't know exactly, after 1 hour, i thought it would last forever then I stop testing). So what is different between: startupdate in foreground-> go to background vs startupdate in background; simulator vs real device?
right after position3, I called following line to open safari
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"http://google.com"]];
But it does not work. So what types of task can be executed here, in background?
I need to keep a service running forever. Some search results say that it's impossible. But some familiar apps did that (Facebook,... keeps service to receive notifications). How can they do that?
Thank you so much,
The exact behaviour of location services has been clarified in the latest update to the Core Location documentation. It now states -
The standard location service delivers events normally while an app is
running in the foreground. When your app is in the background, this
service delivers events only when the location-updates background mode
is enabled for the app. This service does not relaunch iOS apps that
have been terminated.
The significant location change service delivers events normally while
an app is running in the foreground or background. For a terminated
iOS app, this service relaunches the app to deliver events. Use of
this service requires “Always” authorization from the user.
So, it seems that for the best chance of continuing to receive location updates in the background you should switch to significant location change monitoring once you move to the background state and restore full location monitoring once you return to the foreground.
Overview
My compagny is asking me to release an application that can check location of the device every two hours. The app would send these location data through a TCP/IP socket to my server and then receive information accordingly to these very data (straight away, and through the same TCP/IP socket). So I'm not trying to make my application running in background mode continuously (which, actually, seems to be a hot topic in iOS, and it's also not suitable for my project).
Question
In order to be reliable, what is the best practice to achieve this? So, I would like to know:
Since my app is suspended (= inactive), does Apple allow to open a socket to send location when it's woken up by didUpdateToLocation?
How long do I have to perform my send/receive task via my socket?
Should I create a real background task with beginBackgroundTaskWithExpirationHandler and use the 10 minutes allowed by Cocoa to perform my send/receive task?
It is possible (and allowed by Apple) to ask for a 10 mins background task every 2 hours, without human interaction (ie. the user should re-open the app, etc)?
What I achieved/found so far
I added the location key in my Info.plist to be able to run the didUpdateToLocation handler when my app is inactive.
I am able to send and received data through a socket I have opened when my application was in foreground (= active).
I tried to check the backgroundTimeRemaining when didUpdateToLocation is called. I got a very large result number, which seems to be normal because, at this point, the applicationState is not in UIApplicationStateBackground but in UIApplicationStateActive.
These points are not very clear in the official documentation, and I did not found topics related to my specific case.
Thanks for your help.
According to Apple's documentation, you can achieve these by using a very similar approach to the one you described. What I would do is something similar to what is explained in this post by mindsizzlers:
As a recommendation, turn on significant location updates when the app enters in background, so you save battery. You can do this when the app goes to background:
- (void) applicationDidEnterBackground:(UIApplication *) application
{
// Create the location manager if you do not already have one.
if (nil == locationManager)
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
[locationManager startMonitoringSignificantLocationChanges];
}
Then, the system will wake your app when location changes, as explained in the documentation.
If you leave this service running and your app is subsequently suspended or terminated, the service automatically wakes up your app when new location data arrives. At wake-up time, your app is put into the background and given a small amount of time to process the location data. Because your app is in the background, it should do minimal work and avoid any tasks (such as querying the network) that might prevent it from returning before the allocated time expires. If it does not, your app may be terminated.
In order to avoid the action of sending the new location to the server from being highly unreliable (it may work sometimes) you should tell iOS in advance that you are doing a background task that should be allowed to run to completion.
Change your location manager delegate (didUpdateToLocation) to handle background location updates.
- (void) locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
// Send the new location to your server in a background task
// bgTask is defined as an instance variable of type UIBackgroundTaskIdentifier
bgTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:
^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}];
// Make a SYNCHRONOUS call to send the new location to our server
// Close the task
if (bgTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
} else {
// Handle location updates in the normal way
}
}
This way, you will not need to have the timer running. Since you will be waken up automatically and sending the updated location to your server every time it changes significantly.
Of course, if you want to make sure this happens in a specific interval, you can still go with the approach of setting a timer to start receiving location updates and as soon as you get it, you send it to the server. Take a look to this post talking about Background Modes in iOS (section: Receiving Location Updates) and this other questions to see how to do this in detail.
I was wondering if I could send some webservice calls while my application is in the background. How does skype do it? Even if I press the home button my call stays connected.
Building on what rckoenes stated, applications are allowed to register background tasks to be completed after the user hits the home button. There is a time limit of 10 or 15 minutes for these tasks to complete. Again, you can register a task to complete immediately after the user hits home, this does NOT allow you to execute code say an hour after they exit the app.
UIApplication* app = [UIApplication sharedApplication];
task = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:task];
task = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task.
NSLog(#"Started background task timeremaining = %f", [app backgroundTimeRemaining]);
if (connectedToNetwork) {
// do work son...
}
[app endBackgroundTask:task];
task = UIBackgroundTaskInvalid;
});
UPDATE: if your app supports versions of iOS previous to iOs 4, you should also check to ensure that multitasking is supported before registering a background task. Use something along the lines of:
UIDevice* device = [UIDevice currentDevice];
BOOL backgroundSupported = NO;
if ([device respondsToSelector:#selector(isMultitaskingSupported)])
backgroundSupported = device.multitaskingSupported;
Try This... Excellent code for running app in background with no time limit. (I tested it for downloading more than 600 mb data from web-service.)
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UIApplication *app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier bgTask;
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
}
Update ::
you can found more information regarding multitaksing in this apple doc Background Execution.
Please test on device.
It depends or what kind of application are you trying to code.
Skype is registered as a VoIP (Long-running app) app and this is why it can stay "running" although it is on the background.
Apple separates apps in three:
Executing Finite-Length Tasks (you can run tasks for a finite amount of time)
Downloading Content in the Background (you can download content to present it to the user when the app becomes active again)
Implementing Long-Running Tasks (This is the most interesting background apps category, with some subcategories that the developer should define for your app)
Apps that play audible content to the user while in the background, such as a music player app
Apps that record audio content while in the background
Apps that keep users informed of their location at all times, such as a navigation app
Apps that support Voice over Internet Protocol (VoIP) (SKYPE is here)
Apps that need to download and process new content regularly
Apps that receive regular updates from external accessories
So, you need to evaluate in which category your app is and what your service operation performs. Maybe if you're sending some small things to the service the best approach is only to request some extra time on the background for doing the job.
More info about all of this are on this link:
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html