I'm testing with iBeacon for doing some task related Bluetooth in a iOS app after killed.
Actually It works out very well, but I'm still curious how it works.
Here is a code that I used.
private func startMonitoring() {
if CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) {
self.log("startMonitoring")
let region = CLBeaconRegion(...)
self.locationManager.startMonitoring(for: region)
}
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if let region = region as? CLBeaconRegion {
if CLLocationManager.isRangingAvailable() {
self.log("didEnterRegion")
self.locationManager.startRangingBeacons(satisfying: region.beaconIdentityConstraint)
}
}
}
func locationManager(_ manager: CLLocationManager, didRange beacons: [CLBeacon], satisfying beaconConstraint: CLBeaconIdentityConstraint) {
if !beacons.isEmpty {
self.log("didRange")
self.doSomething()
}
}
I called startMonitoring()
once when app is initially started and used two CLLocationManagerDelegate
methods. And also I added log()
for test.
I expected after I kill the app, didEnterRegion
is called first and then didRange
is called and finally do the task.
But it turns out, I just see 3 logs about "startMonitoring", which means (I guess) iBeacon called startMonitoring()
somehow.
How is it possible? Why doesn't the app call delegate methods, and why does it even works out well?
Launching an app based on beacon detection works well on iOS because beacon monitoring is built on top of the same CoreLocation framework functionality as geofence region monitoring. It works like this:
didFinishLaunching
returns, iOS checks if the region state change that triggered the launch is registered with CoreLocation. If so, it calls didEnter or didExit.The sequence in step 4 is critical for making this work — if you re-start monitoring before the end of didFinishLaunching
in your app delegate, you get the didEnter callback.
And, yes, this all works even after killing an app from the task switcher because iOS does not remove an app’s monitored regions when the app is killed. It is one of the few ways you can relaunch and app after that action.
If you are not seeing log lines consistent with the above, there may be an issue with your logging. Try setting breakpoints and you will see the calls made in the sequence I describe above.
See this page for Apple's description of how didFinishLaunching
is called when a CoreLocation change launches the app. That page is specifically for the significant location change service, but the same mechanism applies to beacon monitoring:
If you start this service and your app is subsequently terminated, the system automatically relaunches the app into the background if a new event arrives. In such a case, the options dictionary passed to the application:willFinishLaunchingWithOptions: and application:didFinishLaunchingWithOptions: methods of your app delegate contains the key UIApplicationLaunchOptionsLocationKey to indicate that your app was launched because of a location event. Upon relaunch, you must still configure a location manager object and call this method to continue receiving location events. When you restart location services, the current event is delivered to your delegate immediately. In addition, the location property of your location manager object is populated with the most recent location object even before you start location services.