I have an app that uses LocationManager. For a while I was using Significant Location Change notifications. As I read the documentation, I believed 2 things to be true:
They did not really consume any extra battery because you were basically telling the phone, 'just give me knowledge you already have.'
When the app was off, the notifications stopped.
Once I introduced that, I got a lot of emails from people accusing me of burning up their battery. So I took it out.
Got an email yesterday from a fellow showing me that the location arrow in Settings/Privacy was still purple, even when the app wasn't running. That's also true on my phone.
I can only think of two possibilities:
The registration for significant change notifications is still in tact, despite upgrades to newer versions, etc.
Or, one time the app crashed while the location manager was on and it was never turned off.
2 seems pretty far fetched, as I would think there would be a ton of that happening potentially. I was kind of disappointed that there's no way in Instruments to see what the system believes its obligations to the app are, and when they have been in effect since.
It would be nice to see what delegates are active for an app, even beyond the app running actively.
From what I know about the significantLocationChanges, they work along the same principles as the regionMonitoring. If you have users complaining about battery life, they are likely getting burned by something else. -significantLocationChanges uses fewer items for location than -regionMonitoring does. I believe they are strictly cell-tower related. They may use Wifi detection as well, but they don't advertise it.
Also, the location monitoring will persist even when the app has been killed and between updates and upgrades. I don't know how the app will behave if you upgrade the app and remove the delegate methods while the app is still monitoring for them. My guess is they will get orphaned out there, the callbacks would try to notify your app but the methods are gone and will be ignored. Just a guess, but might be worth testing out. It might be worth leaving those in or a method that will remove all location monitoring. Then remove all that in a later update just to make sure nobody still has any active location code out there.
Related
I'm working on a big app that has a huge Firebase footprint, and is constantly updating to stay in sync with other similar apps and a web service.
The app was designed as a kiosk app, but we discovered users are switching between it and other apps - in some cases often.
While we don't know of any problems, the question came up - when we get moved to the background, should we disable keepSynced, wait some seconds to allow inflight messages to complete, then on coming into the foreground re-enable it?
In a general sense, I don't see any advantage to doing this. iOS and Android automatically manage the lifecycle of apps the move to the background. Backgrounded apps may be killed outright to save memory and battery, and in that case, it doesn't matter what Firebase APIs you may have invoked previously - the app process is dead. I would just let the OS deal with moving things around and not try to optimize any further, unless you have some very specific requirements.
We have an app that heavily relies on being able to access a user's session token using iOS's Keychain. When our app opens, the first thing that's checked is whether the token is available - and if not, we show the user a login screen. We do not use any 3rd party library for this, and use Keychain's SecItemAdd() / SecItemCopyMatching() directly with the following options:
kSecClassGenericPassword
kSecAttrAccessibleAlwaysThisDeviceOnly
We see little to no issues with this during normal usage.
The Problem
We've had users reporting that upon opening their app, they're shown the login screen (suggesting Keychain could not find a value), when they were in fact logged in. We found that in this instance, we found that upon killing and relaunching the app, users were back to normal (session was found in Keychain). Once we found this, we tried added an exponential backoff to keep querying Keychain, thinking that it may had only been unavailable for the first few seconds. This did not work, and proved to us that Keychain seems to be unavailable for the entire app launch session. We are not able to reproduce this issue. It seems to happen very intermittently.
Upon further investigation, we found that commonly, users had received VoIP Pushes prior to having this issue. We're still not able to reproduce this issue reliably, but upon debugging, found that our Keychain.session was found to be nil when receiving these pushes (we rely on it there as well), which is before the user would open their app to see that they are pseudo- "logged out."
We use PushKit and PKPushRegistry in order to do VoIP Push. This requires our app to have "Background Modes: Voice over IP" enabled. We use all of this as suggested by Apple's documentation. Here's a sample:
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
guard let _ = Keychain.session else {
print("Session token not available")
return
}
handle(notification: payload.dictionaryPayload)
}
This code relies on Keychain.session being immediately available upon receiving a VoIP Push. As mentioned before, we've seen instances where it is not available, and so this function logs out and simply returns.
I've also found this relevant Apple forum post, suggesting that this could be an iOS bug.
Can anyone help us with this issue? Any help would be greatly appreciated. Even if this is in fact an iOS bug, we're open to workarounds.
I think this topic is driving the iOS dev community literally crazy since long time. I've read so many articles and thread about this that I lost the count.
So, I'm dealing with this issue too, and based on the #naqi answer, I started moving in that direction and I might have found a working solution.
Assumptions:
The issue seems to happen when the app is getting suspended, due any sort of reason (eg. the app in background from long time and due memory pressure the OS send it in suspended state, or the app is in bg and user upgrades to a new version from the store or automatic app update does it). I'm no longer fully sure what suspended means, but I strongly suspect that the app may get resumed/awaken without actually getting presented on the screen (due bg task, but not necessarily).
One Apple stuff member on the Apple developer forum, had the brave to reply saying something like "try to put a delay between addDidFinishLaunchingWithOptions and appDidBecomeActive". Apart from the madness here, reading that comment made me consider that maybe when the app gets resumed it triggers the addDidFinishLaunchingWithOptions but of course not the appDidBecomeActive (as it may not be resumed by the user but from any sort of event handled by the OS in background, for which we may not have the control seems, or any other feature like bg task or silent push notifications etc).
Extra info:
According with a quite old penetration test article I've found on the web (https://resources.infosecinstitute.com/iphone-penetration-testing-3/) querying the keychain actually would address the query to another process called securityd running on the device, which magically schedule the queries and performs them to the keychain db and, finally returns back the content to the requesting app, assuming that the app is still alive when the query gets completed on the keychain db. This made me think that, if that is still true, such process may fall in sort of "DDoS" attack where your app (or maybe all the apps in execution? Not clear how it actually works) performs too many queries. This may lead to false positive errors like secItemNotFound or similar. Again, as nobody confirmed this, keep it as my personal consideration.
Approach:
From the #Naqi answer, I could exclude the points 2,3 and 4, keeping the point 1 as only still valid reason. Said that, I decided to move any CRUD operation dealing with the keychain, from the addDidFinishLaunchingWithOptions to appDidBecomeActive, by handling the app setup instructions (whatever needed by your app) in there, in order to make sure that eventual background events of suspending/resuming actions won't affect the keychain at all as the appDidBecomeActive would not get called unless user actually makes the app to get opened and shown on the screen. Of course this should happen only on app launch, so you can use a bool flag or similar semaphore approach in appDidBecomeActive to make sure your setup logic is executed only once.
Combined with the above approach, In the App Delegate I also explicitly implemented application(shouldSaveApplicationState:) by returning false and application(shouldRestoreApplicationState: still returning false (Even though I assume by default should be already like this). This may not necessarily be useful but it was worth it to try too.
Summarising, on some affected devices which were randomly failing the keychain access, seems now not to happen anymore since weeks and, consider this was happening for some, almost every 2/3 days let's say, I assume it might have attenuated the issue at least.
I can say that the monitored devices were all in different configurations, with a lot of apps installed or not, with storage memory almost full or not, with a lot of apps in bg or none at all, any form factor and different iOS versions. Keychain backup to cloud not enabled, and items attribute on the keychain at least as kSecAttrAccessibleWhenUnlockedThisDeviceOnly
Open point not fully verified: Supposedly the access to keychain should be thread safe, but I found different thread on the web contradicting each other. Maybe that could somehow impact your logic and cause unexpected state
(Congrats if you managed to read up to here :) )
Hope this helps to further understand what's going on with this keychain tragedy in iOS
I was having similar issues and after talking to engineers at WWDC we identified few issues that is leading to this behavior.
We were calling Keychain too often and this can lead to a situation where it gets expensive and some calls would not finish or timeout
If an app does not provide Access Group for keychain or does not add it in save transaction with keychain then Xcode generates one but this is dynamic and can change between dev environments
If you provided an access group later in the life of your app, then your old keychain value will remain associated with the app and a new value will be created with the access group you will now provided
This also means that when you query keychain you have to provide an access group otherwise you will get a reference to what ever was found first.
Edit: formatting
I want to create an iOS app using React Native. One of the primary features of the app is that it runs constantly in the background. It also requires using GPS btw (in case that is important).
I have had a number of devs tell me its not possible to do this for iOS, however I have read that it is do-able.
Is this possible to do in the iOS environment? Mainly, if the app is running when the phone is powered off, can you make it open when the device is restarted without the user opening it?
I should say that I am a RN novice and any help is much appreciated.
If your app gets permission to get location while it's not in the foreground, then you will get periodic updates and some time to process it.
For example, if you are giving driving directions.
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html#//apple_ref/doc/uid/TP40009497-CH2-SW10
If there is no good user benefit for you to get the location in the background constantly, you might be rejected. Apple suggests region monitoring instead
iOS supports the delivery of location events to apps that are suspended or no longer running. The delivery of location events in the background supports apps whose functionality would be impaired without them, so configure your app to receive background events only when doing so provides a tangible benefit to the user. For example, a turn-by-turn navigation app needs to track the user’s position at all times and notify the user when it’s time to make the next turn. If your app can make do with alternate means, such as region monitoring, it should do so.
I have no idea how RN wraps this behavior, but no matter what it does (or what a plugin might do), the core iOS behavior is how it is described in that URL.
I verified yesterday (at least on iOS 11.2 simulator) that automatically restarting the app (and the location tracking) after phone reboot works.
The key point is that startMonitoringSignificantLocationChanges needs to be on to wake up the app after reboot. For me the difficult part was figuring out when to turn it on, because I couldn't find a reliable way to detect when the phone is being rebooted or run any code at that point. However, based on initial testing it looks like startMonitoringSignificantLocationChanges is independent and doesn't negatively impact the usual location updates, so now I just leave it on all the time and toggle startUpdatingLocation / stopUpdatingLocation on top of it based on Core Motion-detected activity.
Otherwise requirements are the same as for any location tracking on the background, i.e. handle permissions and don't process too much. Apple documentation explains how to detect that the app was relaunched by a location event.
Here's a react native module which basically helps you achieve what you're describing: https://github.com/transistorsoft/react-native-background-geolocation
I am working on xcode 8.3.3 on a project written in Swift2.3.
I installed SwiftLocation (2.3 Branch) library via Cocoapods in order to monitor region events (didEnter/didExit Region).
I had 3/4 region to monitor on average at the same time
I add them as it follows:
do {
let monitor = try Beacons.monitor(geographicRegion: epicenter, radius: 100, onStateDidChange: { (state) in
state == .Entered ? self.delegate?.checkedIn(id, status: .Awake, onFinish: nil) : self.delegate?.checkedOut(id, status: .Awake, onFinish: nil)
}) { (error) in
let error = NSError(domain: “Monitor”, code: error._code, userInfo: [NSLocalizedDescriptionKey : error.description])
self.delegate?.failedBecause(error)
}
monitor.onAuthorizationDidChange = { status in
self.delegate?.movedTo(status)
}
} catch let error {
let error = NSError(domain: “Monitor”, code: error._code, userInfo: [NSLocalizedDescriptionKey : error as! AnyObject])
self.delegate?.failedBecause(error)
}
I have activated Background Modes and asked for Authorization and I am sure it is working in background because the app is woke up and I am able to get events even if the app has been killed.
Moreover both the gps and the internet coonection on the device are turned on. The feature seems to work well, but often the callback “onStateDidChange” of the method “Beacons.monitor(:)” doesn’t get called. It’s really weird that I have a great number of exit event, but not too many entrance.
We already tested if this situation occurs only because the device was inside the region when monitoring was added but this seems to be not the reason. How is possible to have sometimes only the exit event but not the entrance? To intercept the exit event, the system knows that the device was inside the region, but the entrance event hasn’t been triggered.
Any help on fix this or debugging it is really appreciated.
Hi #Gero. Thanks for your answer.
Your scenario is likely to happend.
I simulated it on the XCode Simulator either killing the app directly or waiting more the 5 minutes with the app in background (since Apple documentation suggests that background tasks are killed after 3 minutes). In both situation UIApplicationLaunchOptionsLocationKey is not empty and it tells me that the app has been woken up.
In this case, I execute a piece of code that just instantiate the LocationManager and set the controller as delegate without restarting regionMonitoring.
I have evidences on the server side that didEnter/didExit is called via this flow. I know this because the app makes an HTTP call to store the event and sends “asleep” as a parametern to distinguish it from the “standard” flow in which it sends “awake”.
For this reason I am afraid that this is not the reason, because it would be still not clear why it is sometimes working and sometimes not.
We’ll make a try with requestState(for:) and let you know :wink:
Okay, this is only an educated guess, but since it's too long for a comment I'll try to warp it into an answer. Your comments and (excellent) edit indicate to me you know what you're doing and employ the correct methods (sidenote: A lot of questions on this topic result from improper understanding of the feature, so at first I thought it's maybe that).
What I could imagine happens is this:
The app is running while you're outside of the region, i.e. the next thing would be a .Entered kind of state in the callback. However, the app gets terminated by the OS before that happens for some reason (memory?).
Next you enter the region, so the app gets relaunched. You properly figure this out checking for the UIApplicationLaunchOptionsLocationKey in the launchOptions dictionary and start your region monitoring again. However, I think the system might then not explicitly deliver the state change via callback again. The documentation is not that clear on this for region monitoring. So now you're inside the region, but never received an according state change callback.
The solution would be to explicitly query the region if and only if your app is (re)launched with UIApplicationLaunchOptionsLocationKey using requestState(for:) of CLLocationManager and your region (this might be tedious if you have multiple regions, but is probably required).
Edit: For what it's worth I stumbled over a related question on the dev forums and even clarified the issue further there. Gualtier Malde from the Apple staff answered, so that should be a valid source. In case the link vanishes (as sometimes seems to happen), here a summary and quotes of what he explained.
The basic question was about rebooting (which terminates an app) and then the app not getting region updates. His first answer was
After a reboot, your app will not be alerted for iBeacon events until the user unlocks the device first. There is no way around this.
If you are having issues with iBeacon detection even after the user unlocks the device, barring any coding problems with your app, the first place to look at is the iBeacon advertising, and to make sure it is advertising 100% to the specs, and is also calibrated properly for its installed location.
I then further asked when exactly the resume is about to happen. I wanted to know whether because of this it's theoretically possible to miss region updates and when exactly the region updates should come in again. Answer:
Yes, your app should get relaunched in the background - considering the beacons are advertising correctly, Bluetooth is turned on, etc. - without user interaction.
All this will happen only after the first unlock. Nothing will happen with your app after a reboot until after the first unlock.
Any events that happen before the first unlock can be considered lost to your app.
From this I take that even a relaunched app gets region updates. I have no idea why you miss any, unless you actively reboot the device.
One thing to note regarding your testing procedure though:
Putting the app in background and waiting > 5 minutes will not automatically terminate the app. It will usually simply put it into the suspended state. The documentation you refer to does indeed explain that background tasks are terminated after a certain time (note that they do not guarantee a definite time here, Apple stays intentionally vague here to remain in control for future changes and stuff), but that does not mean your app process is necessarily killed! The tasks are just ended and your app is suspended. There is no real way for us to have the OS terminate an app. AFAIK it would do that if it needs the resources, but that's hard to enforce somehow. Besides, I am also pretty certain that a debug build on a development device (especially connected to Xcode/the debugger) doesn't necessarily behave like an iPhone in the wild. The simulator behaves even more different.
All that said, I am even more surprised that you get an application(_:didFinishLaunchingWithOptions:) call, because the way you described how you terminated the app shouldn't even result in it being terminated in a regular, okay way. Maybe you do have a bug, it crashes (or even gets "punished-quit" by the OS?) or the like? I don't know if an app that for example tries to "maliciously" buy background processing (e.g. by answering the application(_:performFetchWithCompletionHandler:)'s completion handler dishonestly or something?) time might be exempted by the system from region updates? That might be the case, but we're way beyond the scope of this question. It all boils down to an application state issue, i.e. life-cycle problem. You have to be very careful to properly identify what state you're trying to debug and expect to be in.
I have implemented restoration functionality for CoreBluetooth and it works fine, except one thing - after a week (+/- - not sure for 100%) in the background, without opening, app terminated and not restored anymore on any BLE-based events (tested few times).
I also add logging to all BLE related task, add analytic for capturing crashes, track restoring process/events in additional - and after checking this logs/info/reports - not found any exceptions or something that can terminate my app.
The question is - can someone explain me the reason why I got such behavior?
Bluetooth related issues can be really hard to identify. We came across several unexplainable bugs in the past. The only way to determine why BLE was no longer working or misbehaving was to look at the CoreBluetooth logs on the device.
In case you have not tried that already you can enable these logs via a configuration profile that you can download from the Apple Developer Pages (look for Bluetooth and follow the instructions).
As soon as you enable the logging you can download a complete log from the underlying BLE stack that will tell you what went wrong. They even tell you if you have misused the API in some way. Hope that helps.
From my experience with CoreBluetooth State Preservation and Restoration I have come to the conclusion that it does not work reliably. You can get it working sort of "ok", but you will never get it to reconnect reliably while using long running pending connections in the background.
I have reported several bugs regarding this, but the most serious one in my opinion is that If the bluetooth manager switches state while the app is terminated then all pending connections will be lost (since they are not remembered by the module or the iOS stack). Since bluetooth state switches will not cause your app to be relaunched then it simply means that at any point in time your pending connection can be lost and you will not know about it. This on its own effectively means that State Restoration is rather useless. You could say that this is not a bug, but rater a poor design choice…
Another one is that sometimes the framework gets ”stuck” in a bad state (possibly by an internal race-condition or similar). This can happen randomly, but you can pretty easily reproduce this by calling connectPeripheral immediately in the didFailToConnect or didDisconnect callback. When this happens the connectionState will be set to connecting while in fact it is not.
And so on…
/Anton