How can I debug background fetch capability in iOS? - ios

I have an application with background fetch capability. It was one of the first features implemented, and it was working like a charm - calling performFetchWithCompletionHandler every specified time interval (10 minutes).
Later in development, I noticed that this app does not performFetch at all - it was left running for weekend, and it did not fetch even once, while preferred interval was set as 10 minutes.
There was no changes to any of the related code, and I checked that it is set up correctly:
"background fetch" background mode - check
call setMinimumBackgroundFetchInterval - check (straight in appDidFinishLaunching, 10 * 60)
implement performFetchWithCompletionHandler - check
I can trigger fetch via Xcode debug tools, but there is no single call to app while left alone.
I had checked that iOS setting for background refresh is set to "ON" - both global setting (wifi and cellular) and per-app setting.
I did try reinstalling application, changing appID, even purging iPhone completely - nothing did not help.
The most intriguing part is that background fetch in the same build is still working for some testers. All of them have the same latest build, but most of them have no background fetch, while some are still updating in background.
I did not notice any correlation with iOS version, device family, test flight or local build variants or something like that:
iPhone 11 Pro, iOS 13.2.3, TestFlight - works
iPhone 11, iOS 13.3.1, TF - does not work
iPhone 8+, iOS 13.2, TF - works
iPhone SE, iOS 13.3.1, Local - does not work
iPhone 6, iOS 12.4.5, TF - does not work
My next step was to try reverting changes since last build with successful background fetch. Most notable change that happened in that time, was implementation of bundled WatchOS companion app - but how can that break background fetch?
Anyway, reverting source code did not help either.
Even more - I did try to create a simple background fetch example application, pure Xcode template with only three changes for background fetch (mentioned early) - that did not work too.
I am aware, that there is BackgroundTasks API now in iOS - but unfortunately, I have to support older devices with iOS 12 at best. Still, I did try that API too, using BGAppRefreshTask - that did not work too, but I suppose, it has the same underlying logic as old background fetch.
App does not consume a lot of resources - it has around 15 Mb RAM footprint on average, and execution time is at most 5 seconds - only one file download request with timeout of 10 seconds - just in case.
App is not terminated because of resources pressure, or because user force-quit it.
Also, I made sure completion handler is called in time, right after download ends.
The question is:
Am I really missing some step from background fetch implementation? Is it possible to break environment to this extent? If so, how can I find out what is causing all these issues?
Or: it is OK to not have a single bgFetch in weeks? (at this moment some devices did not bgFetch in weeks)

Related

Does Flutter Background Fetch work on iOS when the app is terminated?

I'm building a Flutter app and need to call a function every day (to schedule some notifications). I came across the flutter_background_fetch package, which seems to be a perfect solution for Android, since the main limitation of the minimum 15 minute interval is not a problem.
However, I am supremely confused about how well the package works for iOS. According to the README, "When your app is terminated, iOS no longer fires events". However, I've also seen posts on Stackoverflow that claim that iOS Background App Refresh periodically relaunches your app to run background fetches (and posts that say it is unreliable or doesn't work at all).
So does flutter_background_fetch work on iOS when the app is terminated (eg. double tap home and swipe up)?
Edit: Also exploring flutter_workmanager, which seems promising.
There is no perfect Dart solution for iOS to schedule background task as android when app terminate, in android we can use Alarm Manager
But you can try native side implementation with BGTaskScheduler
https://github.com/fluttercommunity/flutter_workmanager

How to get your iOS app to run in the background for longer than 3 minutes?

It seems like older versions of iOS allowed your app to run for 10 minutes in the background. Now it seems like iOS only allows your app to run for a maximum of 3 minutes. I haven't found any documentation mentioning when, or in which versions, this changed, or any estimates provided by Apple for how long your app can run in the background.
I need to be able to run in the background, because my app is connected to an external device via TCP. I already know about the hacks you can do with VOIP and playing silent audio (both will get your app rejected).
Is there anything I can do to get my app to keep running in the background longer than 3 minutes?
We haven’t had the old 10 minute window for years so that is simply no longer applicable. That was reduced to 3 minutes in OS 7, and further reduced to 30 seconds in iOS 13. (There is a new concept of background tasks, for running tasks longer than 30 seconds, but the the OS will run these at its own discretion, e.g. at night, when the user is charging their device, so that’s not really applicable here).
The short answer to your question is that you cannot submit apps to the store that run in the background indefinitely unless it’s one of a very limited list of permitted background modes.
If you can go to the “Signing & Capabilities” tab of your target and add “Background Modes” and you can see the list of alternatives:
So, with the caveat that you cannot do precisely what you asked, here are a few observations:
The “External accessory communication” background mode (see ExternalAccessory) seems promising, but that’s for bluetooth or wired connections only. So unless you can refactor/reengineer how your devices communicates, that is likely not a viable solution.
There’s an interesting question of whether just keeping the app running in the foreground might be helpful (e.g. judicious use of idleTimerDisabled). I know this solves a different problem (and should be used only where essential), but perhaps it’s option for you.
If your external device is Internet-enabled and you want it to inform the app of some event, perhaps you can have it communicate its intent to some web service that then triggers an APNS push notification that your device can receive.
Bottom line, you can’t do what you ask, but if you edit your question with more details about the nature of the device and what your iOS app wants to do with that device, we might be able to provide better counsel. But in answer to the general question “can I just keep app running perpetually in the background”, the answer is “no.”

Silently Updating iOS Enterprise Apps in Single App Mode

I have a need to update my managed app running on a large amount of iPads without any user interaction. These devices all have the managed app locked in Single App Mode. As I understand it, it's an iOS limitation that an app cannot be updated if either of the following conditions are present:
The app to be updated is in the foreground.
Any app is locked in Single App mode.
With our use case, both of these conditions are present. I also understand that the current workaround is to do the following:
Disable Single App Mode.
Enable Single App Mode for a different app (e.g. Safari) to bring that app into the foreground.
Disable Single App Mode.
Update app.
Re-enable Single App Mode for your app when the update finishes to bring it back into the foreground.
While this method works, it has a lot of drawbacks:
It's highly manual, we have not found a way to automate it. This is especially a problem as the number of devices becomes large. Internet connections can be spotty and there is no robust way to ensure that each step has executed for all devices.
It's prone to failure. Especially between steps 4 and 5 above. It seems there are little to no guarantees of when/if an update was successful to know definitively when to re-enable Single App Mode.
It fails for devices that are offline at time of update. If a device does not have internet connection when the update is executed, there is no way to guarantee that the steps execute fully and in the correct order the next time the device comes back online.
I am looking for a scalable solution to reliably update my managed, Single App Mode app running on thousands of devices in an automated way with no user interaction. I am using Meraki for an MDM right now, for what it's worth.
With iOS 11.2 you can now push a silent update to a device with it running SingleApp mode.
Apple failed to mention this to the public but is included in the beta notes.
Called Enterprise Support today, They could not find any official documentation mentioning ios 11.2.x supports the updating of apps while in single app mode :(
Now with iOS 11.3 beta it is working again but after updation, app is not automatically locking in Single App mode we have to restart device.
Hope in upcoming beta's this issue will get addressed.
iOS 13.1 beta 1 seems to fix most of the issues I have been having in this area.
I had seen issues in iOS 11-12.* where updates sometimes worked, but sometimes showed a pin-pad even though no passcode or guided access code was set, or otherwise behaved strangely.
In the 13.1 beta 1 you can push an App update and nothing will happen on the device, but if you send a restart command afterwards the device will restart, update the App and continue in single app mode.
I have tested with both 'Single App Mode' (SAM) and 'Autonomous Single App Mode' (ASAM) and it has worked every time for me so far.
This works for my use case as you can push the update anytime and schedule the restart at suitable time when the device is not in use.
edit: 13.1 beta 2/3/4 changes this, now when you push an update from your MDM the App will close, update and re-open and continue in single App mode. I checked both SAM and ASAM both now update without the need for a restart.

NSURLSessionDelegate methods not called in TestFlight installs

I am building an app targeting iOS 8.0 that needs to download files that are up to 250Mb. I had a version of the download code roughly working using Alamofire but I recently replaced that with a pure NSURLSession implementation. This new implementation is working as expected in the simulator and on my iPhone 5S running iOS 9.2 (13C75). It works on my phone whether I install through XCode and a physical connection or through TestFlight. I have deleted the app, restarted the phone, and reinstalled and it always completes downloads correctly. One of my colleagues pulled the code and was able to successfully simulate it from XCode.
However, downloads fail on all of my collaborators' devices. They are installing the app through TestFlight and are set up as internal testers. One collaborator also has a iPhone 5S running iOS 9.2 (13C75). The Alamofire-based implementation worked as intended when deployed through TestFlight to those same devices and very little other code has changed.
I have added some remote logging and I can see that on the failing devices, downloads are correctly triggered and the download tasks I create each have a taskIdentifier which I can log. However, none of the NSURLSessionDelegate or NSURLSessionDownloadDelegate methods are called.
What suggestions do you have for troubleshooting?
Is it possible that this could be related to a TestFlight problem? My current next step is to try an alternative to TestFlight. Ultimately, I would like to be able to deploy betas through TestFlight if possible.
I'm using Swift 2.1.1 in XCode 7.2 (7C68).
The problem turned out to be that I had set the NSURLSessionConfiguration discretionary property to true. The docs state the following which sounded good to me:
When transferring large amounts of data, you are encouraged to set the
value of this property to true.
But I failed to appreciate the consequences of the rest of the text:
For example, the system might delay transferring large files until the device is plugged in and connected to the network via Wi-Fi.
The reason that installations through XCode were working was presumably that the phone was either being charged because it was connected to my computer when I started a download or had just been charged.
I initially suspected that the problem came from either TestFlight or something about the state of the phone. Remote logging with Sentry (which I already use for the backend) was very helpful for getting some insights into what was different when my collaborators ran the app. I eventually added in implementations for all the NSURLSessionDownloadDelegate methods to log which ones were called. This led me to see that didReceiveChallenge was the only delegate method being called and I then spent some time thinking that it could be an auth-related problem and troubleshooting in that general area.
Finally, I started noticing that my own install would fail sometimes and one collaborator had a successful download. That made me start thinking about the kind of phone state that could prevent a download. I went back to the NSURLSession configuration that I had and that's when I read about the discretionary property.
In retrospect, carefully checking the configuration would have been a good step to do earlier. Another good thing to do would have been to log the state property of the download tasks that were created.

SendMessage Complication Controller Wake iOS App

So, I am trying to use the oppertunity when my Watchkit complication calls requestUpdateDidBegin() to use sendMessage() to wake my iOS app and cause it to calculate complication data and use transferCurrentComplicationUserInfo() to update the complication. I call sendMessage() from the extension delegate. I am experiencing mixed luck with this actually reaching the iOS app. Even with my iPhone next to the watch the performance varies. One time I built my app to my phone and it successfully delivered over 30 updates to my complication through sendMessage() and transferCurrentComplicationUserInfo(), over the course of two days. I then uninstalled the app from the watch using the watch app, and then reinstalled it from the watch app (So absolutly no code or build changes) and it stopped working! I suspect there is clearly some bugs and unreliability with calling sendMessage(). Is anyone else finding this, is there any workarounds? How can I wake my iPhone app from when the complication controller calls requestUpdateDidBegin()?
I don't believe there is any guarantee you can wake the iPhone from the watch. If you need to update with data from the phone, you are better to schedule the updates on the iPhone app and then push the updated data from the phone. You'll probably need to enable a background mode on the iPhone app for this to work (location services, background data transfer, depending on your data type.)
(As posted here https://forums.developer.apple.com/thread/26934)

Resources