iOs Swift: How to pass a task to the background thread? - ios

In my iOS app I have a task that is executed perodically with a timer (the task is checking my database every 10 seconds):
var timer: dispatch_source_t!;
timer = Dispatch.timerAsync(interval: intervalTime){
//my task here
}
if a speciifc parameter in my database is set then I stop the timer and process the results (including updating the UI) in the main thread:
dispatch_async(dispatch_get_main_queue()) {
self.processResults();
}
Now what I'm trying to do is to make this work when the user switches to another app. When my app goes to the background the timer stops working and when I go back to my app it starts working again. I wanted to know if it is possible to pass this task with the timer to the background thread when the user switches to another app so that my app still checks the database every 10 seconds and if the parameter in the database is set the user gets a notification (local notification). I would like to avoid using Apple's remote notification if possible... Is this possible?
I don't have enough experience with app programming so I wanted to get some idea to see if I'm approaching this problem the right away. Any input, suggestion or sample code is very appreciated. Thanks!

Related

How to continue a Timer once an app enters background [duplicate]

This question already has answers here:
How to call a function when the app is inactive (e.g. playing music in background)?
Swift 3 - How to make timer work in background
(10 answers)
Closed 2 years ago.
I am trying to execute a simple function that runs a Timer in the background of the app.
In short,
A 10 second Timer starts as soon as app begins
I lock the device after seeing confirmation the Timer has begun in the Foreground
10 seconds later, with the app in the Background, I should expect a log to appear. This currently works in the simulator but not on the device
Full code below. You can also download the app itself to try.
After consulting popular answers such as this (which also contains conflicting answers about whether a Timer actually runs in the background or not), I am not exactly what the authoritative answer is on at the topic.
Any help is appreciated.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var timer : Timer?
if timer == nil {
NSLog("Timer started")
timer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true, block: {_ in NSLog("After 10 seconds show up") //this will not work on a device
//Also, I get the error: Can't end BackgroundTask: no background task exists with identifier 1 (0x1), or it may have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug.
})
}
}
}
It may be confusing, but questions like that actually do answer the question. Bottom line, the app is completely suspended (including timers) when the app enters the background. You can, however, request a little time before it is suspended as outlined in Extending Your App’s Background Execution Time. You’ll see many online references to this being able to allow an extra three minutes of background execution before it is suspended, but in iOS 13 this has been further reduced down to only 30 seconds.
Now, apps that have legitimate need or background execution (e.g. a navigation app, a VOIP app, a music playing app, etc.) can request special background execution modes (see About the Background Execution Sequence, but none of these are intended solely for the purpose of keeping a timer running in the background.
If you want to notify a user at some designated time in the future, use user notification service. But do not just attempt to keep your app running in the background.
By the way, while that link describes the process, their code snippet is misleading. See https://stackoverflow.com/a/23831862/1271826 for a more contemporary rendition.

Prevent iOS from killing App after a few minutes

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.

objective-c differentiate between alert message and task switcher in applicationWillResignActive

I am trying to run some code during the applicationWillResignActive when the user opens the task switcher and it has worked fine until I began using bluetooth in my app.
When bluetooth tries to connect to a device it shows an alert window asking if the user wants to pair the device. This alert is enough to trigger the applicationWillResignActive method and then runs my code for when the app is being navigated away from (task switcher). This causes a large problem since the code I intend to run when switching away, turns off some much needed functionality within the actual app. So once they press "pair" or "cancel" on that alert, all of my app stops functioning as it should because the app has lost focus.
I have tried to detect the state of the application during this time with this... NSUInteger state = [[UIApplication sharedApplication] applicationState]; thinking of course that it would be considered active when the alert pops up and inactive when in the task switcher. However, this was not the case it shows up as active for both use cases.
Update #1
The question...
How can I differentiate in the application between the app causing a system level inactive focus state like running code to connect to bluetooth, versus the user causing the system level inactive focus like double tapping the home button? All in the efforts to distinguish what is causing the applicationWillResignActive method to fire.
Update #2
The intention of this functionality is to set a flag in NSUserDefaults when bluetooth connects to the device. This flag is being "observed" and used to trigger the changing of view controllers to a page related to this new BT connection. When the user double presses the home button and moves to task switcher I turn off BT and switch to iBeacon so I can notify of events. All is well with this current implementation all bar 1 use case.
If the user hasn't yet connected to the BT device and it connects for the first time and that pairing alert comes up it fires the applicationWillResignActive method just the same as double tapping the home button does. In this method the code then checks for that NSUserDefaults flag to see if it switched on (which by this time it is because the BT has already reached the CBCentralManager's didConnectPeripheral method and turned it on) and if it's on, it turns off BT and switched to scanning for iBeacon. Because the app is still open this obviously causes problems. The app is running so the user see's the BT connect, the new view slide in, the pairing alert come up, then the new view slide right back out and iBeacon starts sending notifications intended for when the user is in the task switcher.
I already have this exact functionality happening in the applicationWillEnterBackground method so that's not the answer. I need to have a way of saying "the app is running right now and we've received an alert instead of double tapping home, so please don't turn off BT and turn on iBeacon yet"
Two possible solutions:
1. The answer may lie in this statement:
When bluetooth tries to connect to a device it shows an alert window asking if the user wants to pair the device.
Your app must do something to cause this alert to appear. You could set a Date field to the current time in your AppDelegate when this happens, and then when you get a call to applicationWillResignActive you can compare that timestamp to the current time, and if it is < 1 second or so, you have a pretty good clue that the bluetooth dialog went up.
Of course, this is not foolproof. As #danh notes in his comment, the design of iOS makes this really difficult. You won't know for sure if the bluetooth dialog went up, or if the user or OS just happened to bring something else to the foreground at the same time. What's more, it's always possible that even if the bluetooth dialog comes up, the user might decide at that very moment to go check his or her email or start browsing Facebook. In that case, it is both true that the bluetooth dialog is what sent your app to the background, AND the user navigated away from the app. Unfortunately, iOS doesn't really give you a way to differentiate the two.
2. You might use a background task to handle your cleanup logic.
You can request up to 180 seconds of background running time after the call to applicationWillResignActive, so you could defer your cleanup tasks until say 175 seconds have passed since your app is resigned to the background. If the user doesn't come back within 3 minutes, it's probably time to do this cleanup anyway. My blog post here shows the basics of setting up a background task. It is specifically targeted to extending beacon ranging time, but you can put whatever logic you want inside the background code block like this:
- (void)extendBackgroundRunningTime {
if (_backgroundTask != UIBackgroundTaskInvalid) {
// if we are in here, that means the background task is already running.
// don't restart it.
return;
}
NSLog(#"Attempting to extend background running time");
__block Boolean self_terminate = YES;
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:#"DummyTask" expirationHandler:^{
NSLog(#"Background task expired by iOS");
if (self_terminate) {
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
_backgroundTask = UIBackgroundTaskInvalid;
}
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Background task started. Waiting 175 seconds before cleanup.");
[NSThread sleepForTimeInterval:175];
//TODO: perform cleanup code if app is not in the foreground by now
});
}

iPhone app background downloads

I'm making an app that receives constant updates (potentially hundreds of times a day) and, to make for a better user experience, it would be nice to have these downloaded in the background.
Looking at Apple's[1] documentation I need to set the background mode to "Background fetch". Exploring deeper you can read about the application:performFetchWithCompletionHandler[2] function which states that:
When this method is called, your app has up to 30 seconds of wall-clock time to perform the download operation and call the specified completion handler block... If your app takes a long time to call the completion handler, it may be given fewer future opportunities to fetch data in the future.
The problem is our downloads will take longer than 30 seconds to download, and as such would rather not face the wrath of Apple sending updates fewer and farther between, thus exacerbating the issue!
So, am I able to do this somehow?
Also, I have created a crude experiment whereby I create a NSTimer:scheduledTimerWithTimeInterval to run every minute which logs to the console. This successfully works both on the iPhone in simulation (has been running for 30 mins plus) and also when I place it on a phone (a week plus)... why would this be!?
It may be hard to do because of the Apple 30s obligation. They decided so to eventually prevent big download to happen not to drain battery and data plan.
You must be sure you really need to download that much data (as it takes this long) in background, plus hundred times a day!
I mean, when your app goes foreground after a (long) background period, it may not be updated and it's normal case. So you need to do the update when the app goes foreground; only one update is needed. Otherwise, you should step back and rethink the update process.
Found a solution:
You can bypass Apple's application:performFetchWithCompletionHandler function and set your own timer. To do this make sure you do the following:
Have selected Background fetch under Your app settings > "Capabilities" > "Background Modes".
In AppDelegate.m, within application:didFinishLaunchingWithOptions, add the following code:
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
// Nothing
}];
You can now add a NSTimer in your AppDelegate and it will continue to run whilst in the background. E.g.,
_timerBg = [NSTimer scheduledTimerWithTimeInterval:1800
target:self
selector:#selector(bgFunction)
userInfo:nil
repeats:YES];

Not calling didBecomeActive if background task expires

Here is the situation:
I am picking a large video using imagepicker. Obviously the picker will take a bit of time to compress the video. So to ease user experience I have enabled background task for it.
Now here comes the issue:
If user choose a video and and tap the home button, application goes to background and continue compressing video for next 600 secs. And the background task expires. In the expiration handler I have stopped my background task.
Now if the user is resuming app after the background expiration
- (void)applicationDidBecomeActive:(UIApplication *)application
is not being invoked. Can anyone explain me why this happens?
When the background tasks expires, your app will really be closed! So it's not becoming active again, it's launching.
You should handle stuff in your expiration handler or/and when your background task ends successfully. Both situations, you need to set the background_task as invalidated.
If your app goes to background while converting the video, and then user open it again BEFORE the task end or the background task expires, then you should see the app calling applicationDidBecomeActive.
I assume you know it, but maybe you are missing the multitask properties in your Info.plist file, so your app isn't accepting background tasks the way you expect.
-(void) applicationDidBecomeActive:(UIApplication)application
This method Only called when the app's sate is changed from inactive state to active state.
Is it possible to know whether video picking finished? if it's possible then just store it. and when the user comes again to the app. just fire the functionality you required.
Could you try to add log statement to method applicationDidFinishLaunching? May be the app terminates or crashes before a user opens it.
Also, I think correct way is to save current parsing context when app receives signal like applicationDidFinishLaunching and when app starts resume parsing. Because a user can close the app manually.

Resources