PeopleInTheKnow!
I'm writing this app, that uses Background Fetch, to see if there's some new information on a server.
Background Fetch works fine, but is pretty unpredictable in when it performs its trick.
Therefore, I though I should use scheduled UILocalNotifications, so I have more control over the exact timing and frequency of the execution of the method associated with Background Fetch.
But this is where I get confused:
My scheduled UILocalNotifications fire as expected. But the delegate
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
// I recieved a notification
NSString * notificationType = [notification.userInfo valueForKey:#"dailyCheck"];
NSLog(#"Received Notification of type %#",notificationType);
}
in which I would like to call the server check method, is only called when the app is active.
This seems to make such a mechanism useless for my case.
Could any of you advise me what would be the best approach, to make sure that my app, be it in the foreground or background, will check the server every day at a a specific time?
I should add, that I don't expect any user interaction here. So, no alerts with buttons or anything like it.
Thanks ahead
Related
With UIBackgroundRefreshStatusAvailable, my app receives silent push notifications as expected, that is both in background as well as in foreground.
The name of this feature -- Background App Refresh -- suggests to me that it does not affect Foreground app behaviour when it is disabled.
Unfortunately, my app does not receive silent push notifications while it is in the foreground, with background app refresh disabled, i.e. UIBackgroundRefreshStatusDenied.
I'm not using user-visible push notitfications, hence no involvement of UNUserNotificationCenter etc.
Is it possible to receive silent push-notifications with background app refresh disabled, i.e. UIBackgroundRefreshStatusDenied?
Objective-C, Xcode 11.3.1, deployment target iOS 10.3. iPhone 6 with iOS 12.4.5 installed.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[application registerForRemoteNotifications];
return YES;
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// not called when app in foreground but bg app refresh turned off
}
Here is an example userInfo dict that didReceiveRemoteNotification received when bg app refresh was enabled:
{
aps = {
"content-available" = 1;
};
ck = {
ce = 2;
cid = "iCloud.de.udo-thiel.DiskBench";
ckuserid = "_56bd97c2eb1e52d09756163efaab6b02";
nid = "e70e4a8d-d77b-4315-8b3e-d9de229cf083";
qry = {
dbs = 2;
fo = 2;
rid = "Res-iPad 2-16";
sid = "public-results2";
zid = "_defaultZone";
zoid = "_defaultOwner";
};
};
}
This is an interesting question as it basically addresses the issue of naming this quite complex functionality.
In short, I am afraid the answer is no. While I haven't had to deal with this myself I have witnessed many others in the same position as you.
To me it helps to think of the entire "silent remote notification feature" as having less to do with the app being in foreground or background, and more with "remote app input":
If it is activated, i.e. you have UIBackgroundRefreshStatusAvailable, your server can send it, silently, in this case, messages on which is reacts. Basically, the server gives input in a similar manner as a user taps (albeit via different called functions, obviously). It doesn't matter whether the app is in foreground or background, this input happens.
If the feature is inactive, i.e. you have UIBackgroundRefreshStatusDenied or UIBackgroundRefreshStatusRestricted, the entire feature is switched off. That means this way of receiving input doesn't work, application:didReceiveRemoteNotification:fetchCompletionHandler: isn't called at all. This method name reflects the issue better than the state enum cases do, admittedly.
Two workarounds:
The most obvious one: register and deregister for remote notifications in applicationDidBecomeActive: and applicationWillResignActive:. Unfortunately this might turn ugly as it results in your server having constantly changing tokens for users, but if you want to avoid getting notifications in background at any cost, go this way.
Register for notifications once and indeed let your logic handle notifications in the foreground and background as you (seem to) already do, but simply make it so it ignores notifications while it is in background (using UIApplication.shared.applicationState). Technically this "wastes" a bit of runtime as your app might get woken up and then not do anything meaningful, but I think that's not too bad.
I'd go with option 2 myself as I rarely see a case where it hurts to receive a silent notification in background.
Generally I would not do anything that relies on notifications being enabled (background or foreground). To put it differently: Yes, if my app is in the foreground and I require to react to something happening on my server, I'm afraid I need to "check" said server, i.e. pull from it in some form.
Or I would, depending on the scenario, inform the user that they should enable it as otherwise the app won't make much sense... Hm...
As a side note: Yup, the Apple SDK is kind of confusing with naming and explaining all the different background things and notifications. Even the app state itself (active, inactive, background, foreground, suspended, ...) is more complex than the names make it seem. I think the reason for that is historical in nature. Before we had background modes and notifications at all, people were just polling data to get something like "silent foreground notifications", basically the thing you want. So eventually they wanted to be able to also do that when the app was not in foreground, asking loudly for background execution. Apple didn't really want to grant that without limits, so slowly the notification concept evolved, but since it was kind of related, the term "background" sneaked in there (besides, we got background fetch as well...), even if it doesn't necessarily make sense.
One could also argue that it's still kind of valid, because it is just more important for when the app is in background/suspended. The use case to "get a silent notification only while in foreground" can still be covered by simple polling (although I agree that this is ugly), and if you use push it doesn't hurt to also get those in background.
The idea behind this app is very simple: download a file. However this app will be for people who are not always within internet access range, so I need it to know that at, say 9:00 AM, to download a file to the hard drive. There will be a button within the app to do it manually as well, but I've already got that working.
As I understand it, this will be difficult if it is even possible. I know that iOS doesn't like multitasking, but I am also aware that it does allow for background timer functions. I am open to any sort of suggestions anyone might have to accomplish this, even if it means writing a separate app. Thanks.
Edit: I see there is the possibility of working with Notifications, or even maybe the Calendar. Ideas in that category as also welcomed.
Edit 2: I also read something about an external server initiating an app, but it gave no description.
Here's the situation regarding background execution and notifications and timers etc. in relation to an app scheduling some activity to happen periodically.
An app cannot execute in the background unless:
It requests extra time from the OS to do so. This is done using beginBackgroundTaskWithExpirationHandler. It is not specified (intentionally) by Apple how long this extra time is, however in practice it is around 10 minutes.
An app has a background mode, the modes are: voip, audio, location, newstand. Even if it has one of these types an app cannot execute without some restrictions. The rest of this discussion assumes the app does not have a background mode.
When an app is suspended it cannot do ANYTHING to rouse itself directly. It cannot previously have scheduled an NSTimer, it cannot make use of something like performSelector:afterDelay. etc.
The ONLY way the app can become active again is if the USER does something to make it active. The user can do this from via of the following:
Launch the app directly from its icon
Launch the app in response to a local notification that was previously scheduled by the app while it was active.
Launch the app in response to a remote notification sent by a server.
A few others: such as URL launching if the app is registered to deal with launching via a url; or if its registered to be capable of dealing with a certain type of content.
If an app is in the foreground when a local/remote notification fires then the app receives it directly.
If the app is not currently in the foreground when a local/remote notification fires then the app DOES NOT receive it. There is no code that is executed when the notification fires!
Only IF the user selects the notification will the app become active and it can execute.
Note that the user can disable notifications, either for the entire device, or just for a specific application, in which case the user will never see them. If the device is turned off when a notification is due to fire then it is lost.
You could use local notifications. They execute code when the user opens the notification that is presented. You can set the local notification to recur at a specified interval (e.g. daily, hourly, weekly, etc). This still requires the user to open the app to get the process started.
UILocalNotification Class Reference
Once the delegate method fires, you only get a few seconds to execute code. Register for a long running background task, and download whatever you need to do. If it can't finish downloading in the 10 minutes you get for the task, then you need to rethink your download strategy.
Apple Multitasking and Backgrounding
We are using this same concept on iOS apps where I work, so this will work if you set it up right.
UPDATE
For those curious how this will work, you just need to implement the UILocalNotification delegate methods. They inherit from the UIApplicationDelegate that should already be in place.
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
// start your long running bg task here and update your file
}
** UPDATE 2 **
Martin H's answer is the most correct so far. But this begs the question, if the user never opens the app, what is the point of downloading data they are never going to see? A recurring local notification reminding them to open the app and update may be the best way, but still requires the user to interact with your app if they want it to remain current and up-to-date.
Background applications have a set time limit (I believe 10 minutes, but don't quote me on that, it could be less) to complete whatever they are working on. You will not be able to use background tasks to do what you want.
What you can do is set an NSUserDefault with the date of the last download. On launch check the date saved, if the date is not the current date, and it is after 9:00am, initiate the download programatically.
I am pretty sure this is not possible, at most what you can do is send push notifications to the user, so that they manually update when required.
Have you tried the following?
if ([[[UIDevice currentDevice] systemVersion] floatValue] >=7.0)
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:600];
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
completionHandler(UIBackgroundFetchResultNewData);
}
In my app I am using a daily silent push to trigger some calculations inside my app and based on the calculation result I am triggering a local notification.
Say for example: A daily goal.
Once the silent notification reaches my app, I trigger a background method to calculate the user data and compare whether his/ her data achieved a goal and if yes I trigger a local notification so that user can open the app and check.
It is not working consistently, sometimes I get it and sometimes don't. When I debugged, what I saw was that the execution get paused in between and execution gets resumed only if the user opens the app again or I trigger a silent push again. Not sure why the background execution (the calculation) gets paused, and if I trigger a push or if i manually try to open the app, I can see the breakpoint appearing back and it continues from the place it was paused.
I am wondering whether it is because of some time limit??
UPDATE:
I am using UrbanAirshipSDK and they have some handlers overridden and I am using the below method to handle the notification. This is written in the appdelegate and gets called when I get a notification when app is in background.
/**
* Called when a push notification is received while the app is running in the background
* for applications with the "remote-notification" background mode.
* Overridden by receivedBackgroundNotification:fetchCompletionHandler.
*
* #param notification The notification dictionary.
*/
- (void)receivedBackgroundNotification:(NSDictionary *)notification;
I first check for content-available in the payload and treat as silent and do the calculations. This works fine intermittently but not consistently. I am closing towards release dates and I really worried.
I assume that you use:
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
In this case you should not forget to call completion handler and according to Apple docs you have only 30 seconds to do so:
As soon as you finish processing the notification, you must call the
block in the handler parameter or your app will be terminated. Your
app has up to 30 seconds of wall-clock time to process the
notification and call the specified completion handler block. In
practice, you should call the handler block as soon as you are done
processing the notification. The system tracks the elapsed time, power
usage, and data costs for your app’s background downloads. Apps that
use significant amounts of power when processing remote notifications
may not always be woken up early to process future notifications.
Im a newbie to iOS. I need some guidance on which is the best way to retrieve json every 5 sec from a remote server both, when in foreground or background, so that the UI gets updated properly regardless of the state. For example: Similar to a live cricket score app for iOS.
Any leads will help me. Thanks.
You should use background fetching, a system-supplied way of efficiently polling. Think in terms of minutes to hours between each poll. Otherwise, use silent push notification: it will wake up the app to do actual downloading. Read on if you are trying to do this for an actual app that you want in the app store.
Facebook app uses this push-to-download AND also abuses this feature to poll to often, thus draining the battery. That is why most people will turn this feature off for Facebook and hence there is then NO polling at all anymore. So be careful with how much energy you consume for a real app.
For fetching data from background you can use BackgroundFectch mechanism.
All you need to do is
In appdelegate class you have to set the time interval.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];//default fetch interval is never,
return YES;
}
Enable the app for background fetch
Target->Capabilities->backgroundModes->backgroundFetch
3.Final step is implement the following method in app delegate class.
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
Hope this will help you .
There is one sample app in Gitgub in which NSXmlParser is used .You can refer that .
You should use NSTimer for repeatedly performing actions::
The following class method will help you:
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)yesOrNo;
Use repeats:YES.
In iOS 7 you have a new background execution method which helps in a situation like this.
Apps that use push notifications to notify the user that new content is available can fetch the content in the background. To support this mode, include the UIBackgroundModes key with the remote-notification value in your app’s Info.plist file. You must also implement the application:didReceiveRemoteNotification:fetchCompletionHandler: method in your app delegate.
So you could send push notification from a server in a timely manner so app can be configured to do the background execution and fetching data.
The idea behind this app is very simple: download a file. However this app will be for people who are not always within internet access range, so I need it to know that at, say 9:00 AM, to download a file to the hard drive. There will be a button within the app to do it manually as well, but I've already got that working.
As I understand it, this will be difficult if it is even possible. I know that iOS doesn't like multitasking, but I am also aware that it does allow for background timer functions. I am open to any sort of suggestions anyone might have to accomplish this, even if it means writing a separate app. Thanks.
Edit: I see there is the possibility of working with Notifications, or even maybe the Calendar. Ideas in that category as also welcomed.
Edit 2: I also read something about an external server initiating an app, but it gave no description.
Here's the situation regarding background execution and notifications and timers etc. in relation to an app scheduling some activity to happen periodically.
An app cannot execute in the background unless:
It requests extra time from the OS to do so. This is done using beginBackgroundTaskWithExpirationHandler. It is not specified (intentionally) by Apple how long this extra time is, however in practice it is around 10 minutes.
An app has a background mode, the modes are: voip, audio, location, newstand. Even if it has one of these types an app cannot execute without some restrictions. The rest of this discussion assumes the app does not have a background mode.
When an app is suspended it cannot do ANYTHING to rouse itself directly. It cannot previously have scheduled an NSTimer, it cannot make use of something like performSelector:afterDelay. etc.
The ONLY way the app can become active again is if the USER does something to make it active. The user can do this from via of the following:
Launch the app directly from its icon
Launch the app in response to a local notification that was previously scheduled by the app while it was active.
Launch the app in response to a remote notification sent by a server.
A few others: such as URL launching if the app is registered to deal with launching via a url; or if its registered to be capable of dealing with a certain type of content.
If an app is in the foreground when a local/remote notification fires then the app receives it directly.
If the app is not currently in the foreground when a local/remote notification fires then the app DOES NOT receive it. There is no code that is executed when the notification fires!
Only IF the user selects the notification will the app become active and it can execute.
Note that the user can disable notifications, either for the entire device, or just for a specific application, in which case the user will never see them. If the device is turned off when a notification is due to fire then it is lost.
You could use local notifications. They execute code when the user opens the notification that is presented. You can set the local notification to recur at a specified interval (e.g. daily, hourly, weekly, etc). This still requires the user to open the app to get the process started.
UILocalNotification Class Reference
Once the delegate method fires, you only get a few seconds to execute code. Register for a long running background task, and download whatever you need to do. If it can't finish downloading in the 10 minutes you get for the task, then you need to rethink your download strategy.
Apple Multitasking and Backgrounding
We are using this same concept on iOS apps where I work, so this will work if you set it up right.
UPDATE
For those curious how this will work, you just need to implement the UILocalNotification delegate methods. They inherit from the UIApplicationDelegate that should already be in place.
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
// start your long running bg task here and update your file
}
** UPDATE 2 **
Martin H's answer is the most correct so far. But this begs the question, if the user never opens the app, what is the point of downloading data they are never going to see? A recurring local notification reminding them to open the app and update may be the best way, but still requires the user to interact with your app if they want it to remain current and up-to-date.
Background applications have a set time limit (I believe 10 minutes, but don't quote me on that, it could be less) to complete whatever they are working on. You will not be able to use background tasks to do what you want.
What you can do is set an NSUserDefault with the date of the last download. On launch check the date saved, if the date is not the current date, and it is after 9:00am, initiate the download programatically.
I am pretty sure this is not possible, at most what you can do is send push notifications to the user, so that they manually update when required.
Have you tried the following?
if ([[[UIDevice currentDevice] systemVersion] floatValue] >=7.0)
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:600];
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
completionHandler(UIBackgroundFetchResultNewData);
}