In order to identify the app termination, i have implemented the Finite-length task but after some period of time app gets terminated and started from start screen.
func registerBackgroundTask() {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(backgroundTask != .invalid)
}
func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = .invalid
if backgroundTask != .invalid {
endBackgroundTask()
}
}
func applicationDidEnterBackground(_ application: UIApplication) {
registerBackgroundTask()
}
app should not be terminated when performing Finite-Length tasks, also please provide examples to identify about the termination of the application when it is in suspended mode.
app should not be terminated when performing Finite-Length tasks
That's not promised at all. There are lots of reasons your app might be terminated. beginBackgroundTask is a request to the OS for more time to finish executing a short-running operation for the user. It doesn't promise your request will be granted.
The specific way you're doing this is likely to fail occasionally, and is specifically addressed in the docs:
Call this method as early as possible before starting your task, and preferably before your app actually enters the background. The method requests the task assertion for your app asynchronously. If you call this method shortly before your app is due to be suspended, there is a chance that the system might suspend your app before that task assertion is granted. For example, do not call this method at the end of your applicationDidEnterBackground(_:) method and expect your app to continue running. If the system is unable to grant the task assertion, it calls your expiration handler.
You want to wrap the specific operation you want to request time for. You always wrap that operation, whether you think you're going into the background or not. You don't just call this every time you go into the background.
please provide examples to identify about the termination of the application when it is in suspended mode.
In applicationWillEnterBackground write a value into UserDefaults. In applicationDidEnterForeground, remove the key. In applicationWillFinishLaunching, look for that key. If it's there, then you're being relaunched, so at some point you died in the background. That includes something like a reboot or power-failure. If it's not there, then this is first launch, or you crashed in the foreground. The precise way to implement this highly depends on what you plan to do with the information.
Related
When app is closed and it gets a push notification, I want to quickly process the notification and show it to user, then let app go back to closed (or maybe suspended). Instead, the app stays running and even tries to load the root view controller.
My code registers for background work, and unregisters when done.
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
func registerBackgroundTask() {
if backgroundTask == UIBackgroundTaskInvalid {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskInvalid)
}
}
func endBackgroundTask() {
print("Background task ended.")
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
I'm able to test my code using Xcode to attach to my app when it launches due to new APNs push notification. After the notification processing completes, app is still alive.
Do I need to force app to suspend?
I thought iOS would just close or suspend the app when it finishes processing the notification and I call endBackgroundTask.
Also, if I close Xcode so app debugger stops, then launch it on my device I see it is running and showing the root view controller (although not fully initialized since that code isn't run for this type of launch).
What am I missing?
First, calling 'registerBackgroundTask' twice will cause 'backgroundTask' to be overwritten and you are going to lose the ability to end task other than the latest one.
Instead, the app stays running and even tries to load the root view controller.
Second, make sure you read about application lifecycle: [1] and delegate methods [2]. Typically your app will be in the background (be it in background execution mode or in suspended state) but it is also possible for push notification to force your app to be launched (in case it is not running at all). It is then up to you to handle it gracefully and to not load the UI in case it is not necessary.
1 - https://developer.apple.com/documentation/uikit/core_app/managing_your_app_s_life_cycle
2 - https://developer.apple.com/documentation/uikit/uiapplicationdelegate
I'd like to prevent iOS from killing my app after a few minutes.
I've read this thread here on SO: Prevent iOS from killing my app after 3 minutes . It says that if I have no backgroundtasks longer than 3 minutes my app wont be killed. Can someone verify that this is true? Because my background-task is not running longer than 3 minutes and even though my app gets killed after this time.
My background-task is a timer that updates a widget. Heres some code:
self.backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
//endBackGroundTask looks like this
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
//
}
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(self.updateTimer)), userInfo: nil, repeats: true)
.
// at the beginning of the class
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
.
// in viewWillDisappear
self.timer.invalidate()
if self.backgroundTask != UIBackgroundTaskInvalid {
self.endBackgroundTask()
}
You need to structure your app so that it doesn't require continual execution in the background. As I understand it, your app shows a count down timer and can show the same count down timer in a Today Widget. The approach I would use is follows:
Store the "end date" for the timer in user defaults to share with your widget
When your app is in the foreground, use a Timer to periodically update your UI
When your Widget is being displayed use a Timer in the widget to periodically update its UI
When your app moves to the background, schedule a local notification for the expiration time
When your app moves back to the foreground, you can cancel that scheduled notification if it hasn't yet fired.
Support app restoration for those cases where your app is legitimately terminated (e.g. due to memory pressure or being suspended for a long period)
If you do this then you never need to call beginBackgroundTask. If you do call beginBackgroundTask and don't call endBackgroundTask within 3 minutes of entering the background, then your app will be terminated, even if you aren't using any CPU.
Short answer: You can't run a background task for longer than 3 minutes unless you are a turn-by-turn navigation app or an audio player. Apple doesn't allow it by design.
Your background task is a timer that is running longer than 3 minutes. So your app is correctly being killed. Consider it confirmed as that is Apple's design.
It's not what your timer is executing that is killing the app, it's the timer itself.
You can read up on Apple's Documentation for more information.
Always try to avoid doing any background work unless doing so improves the overall user experience. An app might move to the background because the user launched a different app or because the user locked the device and is not using it right now. In both situations, the user is signaling that your app does not need to be doing any meaningful work right now. Continuing to run in such conditions will only drain the device’s battery and might lead the user to force quit your app altogether. So be mindful about the work you do in the background and avoid it when you can.
I am working on app where there is an MQTT connection with the server and server is sending some values related to device and UI changes accordingly. But when app is in background user should get local notification that certain values are changed. I know background service are not allowed in iOS but I want to make sure that is that there is no way to achieve this.
I successfully added local notification with app in background by UIApplication.shared.beginBackgroundTask but it's only work for 3 min exact after that apple terminates the app.
func registerBackgroundTask() {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskInvalid)
}
func endBackgroundTask() {
print("Background task ended.")
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
And just calling self.registerBackgroundTask() makes the app runnable in background for three min.
Next that I am going to try is that background fetch and widget to run service, Here I just want some suggestion that is there any chance that one of above two will work ?
It sounds like "Communicating with an External Accessory" would be the background mode that fits your application.
See Apple Docs for reference.
You have to activate Backround Mode for your project and set the value to "external-accessory". Then you can do ongoing small downloads in background. Apple mentions heart rate monitors as an example.
Please note that continous background polling is waste of energy and would deplete battery quickly. Check if this is really needed for your application. If the user just needs infrequent notifications/alarms, remote notifictions would be a much better solution. I use remote notifications in my own projects and it works very smooth and reliable. Additional benefit is, that it would wake up an app even if the user has closed it before.
For more than 3 Minute. You will be enable any mode. Otherwise when app will enter in background app. After 3 min.App will not perform any action.
If I use this method to run a task, but keep app in foreground, will expirationHandler be called while app is in foreground?
I use this for starting location service everytime I enter background, but sometimes user enters background and immediately returns to app, will this call expirationHandler?
func beginBackgroundTask(expirationHandler handler: (() -> Void)? = nil) -> UIBackgroundTaskIdentifier
Documentation says:
A handler to be called shortly before the app’s remaining background time reaches 0. Use this handler to clean up and mark the end of the background task. Failure to end the task explicitly will result in the termination of the app. The system calls the handler synchronously on the main thread, blocking the app’s suspension momentarily.
https://developer.apple.com/documentation/uikit/uiapplication/1623031-beginbackgroundtaskwithexpiratio
It seems that expiration handler is not called in the foreground mode. After very long waiting I went into background mode and then after ~170 seconds expiration handlers were called for all reported previously background tasks.
I have background mode on for location services and aiming to send out location (latitude and longitude) to the server every 30 minutes. For now I am printing the same in the console. It seems to work for a while but I am wondering how do I work with NSTimer in this case. And from where should I be calling it?
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
var locationManager = CLLocationManager()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(application: UIApplication) {
// 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.
}
func applicationDidEnterBackground(application: UIApplication) {
// 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.
self.locationManager.delegate = self
self.locationManager.startUpdatingLocation() // I know i should be using signification location option here. this is just for testing now.
}
func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!) {
self.sendBackgroundLocationToServer(newLocation);
}
func sendBackgroundLocationToServer(location: CLLocation) {
var bgTask = UIBackgroundTaskIdentifier()
bgTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler { () -> Void in
UIApplication.sharedApplication().endBackgroundTask(bgTask)
}
println(location.coordinate.latitude)
if (bgTask != UIBackgroundTaskInvalid)
{
UIApplication.sharedApplication().endBackgroundTask(bgTask);
bgTask = UIBackgroundTaskInvalid;
}
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
application.beginBackgroundTaskWithExpirationHandler{}
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
application.beginBackgroundTaskWithExpirationHandler{}
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
application.beginBackgroundTaskWithExpirationHandler{}
}
}
Maybe calling application.beginBackgroundTaskWithExpirationHandler{} is a bad idea? What options do I go with here?
The idea of beginBackgroundTask... is to start a finite length task so that if the user leaves the app, it will keep running in the background task for some short, finite period of time (3 minutes, I believe). And before the time runs out, you have to call endBackgroundTask or else the app will be summarily terminated.
So, sadly, the background task mechanism is not really suited for your desired intent. There are, though, a narrow set of special background modes designed for continued background operation outside a narrow set of functions (VOIP, audio, etc.). For more information, see the Implementing Long-Running Tasks section of the App Programming Guide for iOS: Background Execution.
Now, one of those background modes is for a "location" service. So, if that is a central feature of your app, essential for proper function, then you can register for the location background mode, and your app will continue to run in the background. From there, you can monitor for location updates, and if a sufficient amount of time has elapsed, trigger some process. But if this background location mode is not an essential feature of your app, Apple is likely to reject your app for requesting a background mode that it doesn't need.
By the way, you should be aware that starting standard location services may drain the device battery. You might consider using the battery efficient "significant change" location service. This also has the virtue of automatically waking your app every time the user moves some significant distance (e.g. measured in km; I believe it's triggered by moving to different cell tower).
Couple of notes, since we've been fighting the location issues and struggling with background applications that don't seem to wake us up.
NSTimer is only good as long as your application is not suspended. Sure, it's in the background, but onlyinasmuchas it is capable of receiving notifications from the iOS (APN, location, ...). Eventually, your timer WILL stop firing while you run in the BG. So, you are relying on one of the BG modes to kick you.
If you ARE using CLLocationManager.startUpdatingLocation, you're going to notice that you quit getting those after a bit. Especially when the phone relaxes and tries to sleep. This is because of a poorly documented feature called CLLocationManager.pausesLocationUpdatesAutomatically ... which decides to stop sending those when set to "true" (the DEFAULT setting). You can set this to "false", and you'll continue to get your position updates. Which will pretty much make sure your app does what you are wanting.
As noted above, you must make sure when you get your locationManager.didUpdateLocations callback that you begin a backgroundTask to keep your application working while you process that information and send your network data. But you seem to understand that.
Just waking up long enough to "record" a location update isn't so bad. Just make sure you don't spawn your network code unless you've clearly hit your 30 min expectation.
try this:
Make a singleton for Location Service. Registered it with a NSNotification
In AppDelegate's willEnterBackGround, you send a notification via NSNotificationCenter
then, your singleton will start updatingLocation and send it to server when location's data received.