After rebooting my phone, my app stops getting CoreLocation delegate callbacks, is it an expected behavior? I think I remember reading that you could still get callbacks. If so, is there anywhere in the documentation saying if its possible or not?
This is not normal. An app is supposed to get callbacks to CoreLocation's methods after your phone reboots, and I have built this feature into multiple apps and verified it works.
Three things make this hard to test, so be sure you are taking these into account:
After an iOS device reboots, it does not perform bluetooth scans to look for iBeacons right away. I do not know the exact timing, but it may take a minute or more.
Even after the above time passes, scans for iBeacons are not happening continually when your app is not ranging for iBeacons in the foreground. Make sure you wait up to 15 minutes (16 minutes after reboot) before you declare that you aren't getting background notifications.
In a typical setup, you won't see your logging statements after a reboot. So it is quite possible you are getting callbacks but there is nothing visible happening as a result. Make sure you do something in your callback for testing, like an unconditional local notification, so you know for sure whether you are getting the callbacks or not.
Related
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 am working on an app that, among other things, provides alarms in emergencies. Users can toggle a setting to have alarms be put through even if their iPhone is muted, but this service has another hurdle to leap: when the app has been force quit, it cannot receive (content-available) notifications until the app is relaunched by the user.
There is a geofencing event in place which buys me some processing time even if the app has been force quit, and in that time, I would like to check if such a block is in place, and if so, request the user to open their app again, and not aggressively force-quit in the future. (Many people still think it's just a way to keep things clean, even though it actually costs you battery life to not just leave apps in the background)
SO THE CORE OF THE PROBLEM: I need an (API call? Something else?) that will tell me whether the app is in such a 'force quit, cannot receive notifications' state, assuming that I do have processing time to do this check.
Anything is welcome, I have not been able to find proper Apple documentation on the notifications block.
Thank you very much.
While there is no API I am aware of to find state after, you can infer the state just before the application is terminated, and record that.
Code
applicationDidEnterBackground
will be called when an app has received a terminate signal.
More Info
This question describes what lifecycle functions to use, and
we have a BLE peripheral that connects to the phone every hour and passes some data. Here is how the process works:
Upon launch with key UIApplicationLaunchOptionsBluetoothCentralsKey in
application(didFinishLaunchingWithOptions launchOptions) app re-initializes CBCentralManager with ID that was passed to it.
Then it goes through the regular restoration cycle and reads data off the BLE peripheral.
Performs REST request to the service in the cloud.
Assuming that app has been launched at least once after phone reboot everything works well for a few days (if app isnt running or been forced out of memory, iOS properly starts it up again, assuming user didnt do forced close manually).
However every few days iOS stops waking up the app when there is an incoming request from BLE device. If user relaunches app everything works properly for a few days and then stops agains. Given the nature of our product, it's critical to have our app / peripheral working together in the most reliable way possible.
Theories as to why it might be happening:
(upon closer examination all of them were dismissed)
Users restart the phone and forget to relaunch the app.
We've added logging of the uptime and it showed that phone didnt restart in between app launches.
Memory warnings lead to app being booted out.
Once again, added logging, they showed that there was no applicationDidReceiveMemoryWarning
Bad connection leads to app running for longer period than 10s when uploading results and iOS terminates it and gets upset
We artificially delayed server response by 15s to test this and everything continues to work properly during testing.
Any ideas on what is happening and why iOS stops notifying app about incoming BLE connection?
One of the problems is that we cannot figure out how to reliably reproduce the issue So any suggestions there will be much appreciated as well!
Thank you!
UPDATE 1:
Here is how we initialize CBCentralManager:
self.centralManager = CBCentralManager(delegate: self, queue: nil, options: [
CBCentralManagerOptionRestoreIdentifierKey : MyCentralManagerID,
CBCentralManagerOptionShowPowerAlertKey : 0])
I saw some suggestions that queue parameter should not be nil. Given that I'm unable to reliably reproduce issue I'm hesitant to make that change until I can confidently observe its effects.
I wanna start by saying that I have been working with CoreBluetooth for a long time now and from what I have noticed CoreBluetooth State Preservation and Restoration does not work reliably at all. You can get it working sort of "ok", but you will never get it to reconnect reliably unless Apple fixes it some day.
There are so many bugs that causes this to not work properly, but I will give you one that I believe is causing your problems:
State restoration will only relaunch your app due to bluetooth related activity if the event originates from a peripheral accessory that you are communicating with, such as connect/disconnect events and characteristics notifications. For other events, most importantly general bluetooth state change events, your app will not be relaunched and notified of this. The reason why this is so bad is because all bluetooth state change events will cancel all pending or current connections, meaning that pending connections will be dropped and your application will not be notified of it. This effectively means that your application will still believe that the connections are still pending when in fact they are not. Since your application is terminated at this time, the only way for it to wake up again is by having the user manually launch it again (or alternatively “hack” other background modes for this purpose, which does not work very reliably either).
This thing happens if the user toggles Flight Mode, toggles Bluetooth, power cycles the iOS device, or any other undefined reasons that many cause state changes…
But this is only one bug. Many other exists as well, such as the XPC connection being interrupted at different times for no apparent reason. I have also noticed that the pending connection can go into “limbo” mode where the peripheral state gets set to Connecting, but in fact it will never connect unless you cycle the connection state.
Anyhow, I am sad to say it, but if you are developing an app that must rely on the peripheral being reconnected in the background then I would not recommend doing it. You will be frustrated. I could probably write an essay about all the bugs in Core Bluetooth that Apple does not want to fix. Even more strange is that you can pretty easily ruin the bluetooth connectivity globally on the device from one single app so that no app can use bluetooth until the device is rebooted. This is pretty bad since it goes against Apple's own Sandboxing principle.
I am designing an iOS framework to handle multiple BLE devices (all of the same kind). Everything is working very well at the moment, except one thing:
The client wants a list with available devices. But how can I detect when a device, that has been discovered in the past, is not available anymore?
Another problem occurs, when I try to connect to a device that is not available anymore. Documentation says: Connection attempts never time out and
And yes, I never get an error via didFailToConnectPeripheral.
I did some research but couldn't figure out how handle these problems via CoreBluetooth properly. So I developed my own solutions, but I am not sure if that is the right way (or at least a good way, cause there may be several ways to do it).
1. Detecting devices that are not available anymore
I scan with
[_centralManager scanForPeripheralsWithServices:services options:#{CBCentralManagerScanOptionAllowDuplicatesKey: #(TRUE)}];
so I receive advertisments all the time as long as a device is not connected. I check with a timer that the advertisement reoccured in a given time interval (large enough corresponding to the devices ad interval). If the advertisement didn't occur in the interval, I remove the device from the list.
2. Detecting connection timeout
Well, that's a pretty easy one I think. I use my own timeout function and cancel the connection request if the timer expires.
If somebody ever came across these problem, I would be very interested in your opinion and/or your solution of course.
UPDATE 2014-12-17:
In the meantime I worked on my own solution using timers and it seems to work pretty well.
Connection timeout is straight forward. Simply set a timer to 5 seconds or whatever you think is good for you. If the timer expires and the device did not connect, simply cancel the connection and tell the user that there was a problem.
Detecting devices that go out of range was a bit trickier. For every discovered device I start a timer that fires after double the time, the device sends advertisements. If the device does send another advertisement till the timer expires, it probably went out of range or was turned off or connected to another device.
I don't want to answer my own question because I hope that maybe Apple will one day take care of those problems.
The correct way to determine whether a device is available is to store the peripheral identifier value. Before you attempt to reconnect, call retrievePeripheralsWithIdentifiers. However, this still does not guarantee that the device will be in range by the time you attempt to connect!
Connection attempts do not time out at the OS level, and this is explicitly documented.
Some apps may need to use the Core Bluetooth framework to perform
long-term actions in the background. As an example, imagine you are
developing a home security app for an iOS device that communicates
with a door lock (equipped with Bluetooth low energy technology). The
app and the lock interact to automatically lock the door when the user
leaves home and unlock the door when the user returns—all while the
app is in the background. When the user leaves home, the iOS device
may eventually become out of range of the lock, causing the connection
to the lock to be lost. At this point, the app can simply call the
connectPeripheral:options: method of the CBCentralManager class, and
because connection requests do not time out, the iOS device will
reconnect when the user returns home.
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.