iOS didEnterRegion sometimes not get called - ios

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.

Related

Appropriate way to get user's location(also in killed state) for an "hyperlocal" app

Requirement - I am building a hyperlocal app which will provide offers to user based on his location. When in app I can get his current location and show offers accordingly but what I need now is to send push notification to user based on his location. So I want to find out user's location and send offers based on his location.
I have read Apple doc for Significant-Change Location Service but then this answer is saying that it won't work once app is killed.
I have also read about Tracking the User’s Location but that didn't work for me properly. I was not getting more than 5 updates in background.
My current code in viewDidLoad -
if (self.locationManager == nil)
{
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.delegate = self;
self.locationManager.allowsBackgroundLocationUpdates = true;
self.locationManager.pausesLocationUpdatesAutomatically = false;
if ([CLLocationManager authorizationStatus] != AVAuthorizationStatusAuthorized) {
[self.locationManager requestAlwaysAuthorization];
}
}
[self.locationManager startUpdatingLocation];
And my delegate method looks like this -
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
if (UIApplication.sharedApplication.applicationState == UIApplicationStateActive) {
// Get updated data according to location
} else {
// Send location to server
}
}
My apps capabilities And my plist -
Please suggest some appropriate way, I can live with accuracy of around 1km.
Another approaches -
Can I get user's location in "Background Mode- Background Fetch"'s fetchNewDataWithCompletionHandler: ?
Can I get user's location using Silent Push notification's application:didReceiveRemoteNotification:fetchCompletionHandler:? ): Not possible according to this answer
So, your question is basically "How can I get around Apple's specifically designed user interaction for people wanting to make my app shut up?"
The answer to that is "Not if you want to stay within the rules for the AppStore".
Listen, if the user decides to terminate your app (i.e. they swipe it up and out of the task manager), the entire point of this design is for your app to stop do anything. Apple designed this so that people can quickly turn off exactly the kind of behavior you seem to want to implement. I don't quite get why you would want to circumvent that. I agree that this interaction is a bit obscure perhaps (after all, users can also allow/disallow an app's right to receive location updates in the background in the Settings app as well), but that's the interaction iOS defines.
If I understand you correctly, you succeeded in properly setting up the background location modes, so your app can receive the location updates even if it is not in the foreground (i.e. in background or suspended, in the latter situation iOS wakes it up briefly so you can process location updates and e.g. send a local notification to inform the user). That's as good as it gets.
Oh, and don't fear a device reboot. Yes, after the reboot your app is technically not running, but since the user didn't explicitly kill it the last time, iOS treats it like it was in suspended mode, IIRC, so you will still get significant location updates and can react properly.
(In a more general way: people often seem to think the actual app process state reflects the app state as it is defined in the documentation and/or that whether the app is shown in the task manager is linked to that. Both isn't entirely true.)
Edit after your comment asking me specifically about background fetch:
Sorry, that was perhaps not entirely clear. I didn't answer on this sub-question specifically, because after the user quits your app intentionally, you should not, as explained, "cheat" on their intention. Silent push notifications won't work because of this, yes.
I don't know whether the background fetch will be suppressed in a similar way (could be, but I haven't tried it), but I think that won't help you either, even if it were still working (which I doubt, Apple's probably going to be hard on this). I have also never tried to (re)start location updates in this method, so I can't say whether that even works (once the completion handler is called the system will likely suspend your app again at least, and I am not sure that this "resets" the "user killed the app flag" that the system seems to use for deciding which app to wake up and deliver location updates to).
application:performFetchWithCompletionHandler: (as it is fully called) will be called by the OS based on heuristics, it tries to be "clever" on this. Since the method is meant to fetch some data from a backend that doesn't provide push notifications, the times it is called at all can be severely limited, as is explained in the documentation. The actual mechanism that decides when the OS calls is a black box and it is a gamble to try to trick it into getting called when you need it. Could be very likely that this happens hours later. Think of the following scenario:
Your app runs fine in the background location modes (I understood you correctly in that you successfully set that up? see here and here)
The user manually kills your app. For the sake of this argument let's assume that doesn't keep the system from later restarting it to give it a background fetch opportunity
Since other apps are running on the system and the user only has an edge connection at the moment, the OS decides it is a bad time to initiate a background fetch (I just assume here that those factors play a role, as said it's a blackbox, but those appear reasonable to me). Your user walks around some more, bypassing several locations you would be interested in.
Two hours later, the user is by now in a completely different area, your app is started again by the OS and gets a application:performFetchWithCompletionHandler: call. You start location updates again (lets assume that even works and the system doesn't immediately terminate the app again, i.e. even after the fake background fetch it delivers location updates in background). You missed several locations, but nevertheless the app now handles new ones. All your logic is basically messed up, because you didn't plan for so many location updates being missing...
(Optional: After a while your user realizes that your app apparently does something, even although they terminated it (for example they notice battery drain). They will delete your app and leave a one-start review...)
(Optional 2, worst-case: Once apple realizes it is possible to re-init background location updates after a user killed an app this way, they simply close the loop-hole in an iOS update and your app is back where we started...)
For the record: I am not defending any design decisions made by apple, heck I know this is confusing to wrap your head around and just as one can make a stand for "preserve battery and user intent under all circumstances" one can make one for better background tracking. I am merely pointing out that they're in control here and trying to weasel around any specific interaction paradigms they set on the platform is likely not a good idea.
That all being said, I am afraid the "user terminated the app, now I won't get any location updates" is simply something you have to live with. Why would that even be a problem for you, considering you didn't say anything about background location modes not being enough for you? The only (questionable) scenario I could imagine is a kind of tracker application, maybe given on devices handed out to employees of a delivery service or something. If that is it (and putting aside the ethics behind such stuff, in some countries that is even illegal to do, mind you...), I have to say that iOS is simply not the correct platform for this.
Hi just create a UILocalNotification set it with your details and most important for notification to be triggered when you enter a Region add the region property see docs
Hope it helps

BLE Restoration/Preservation unexpected effect

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

How to check whether force quit has disabled notifications

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

iOS stops waking up the app upon incoming BLE connection from peripheral

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.

iBeacon monitoring/ranging after phone restarts

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.

Resources