Search code examples
iosswiftcore-locationcore-bluetoothibeacon

(iOS) How does waking up by iBeacon work?


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?


Solution

  • 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:

    1. When your app registers a Region for monitoring, the operating system remembers your app and the region, adding this pair to an OS-level tracking list.
    2. Whenever iOS senses a location change (lat/lon for CLCircularRegion monitoring or BLE advert packets for CLBeaconRegion monitoring), it compares the change against this tracking list.
    3. If a change in state is detected, iOS checks if the app is running. If so, it calls the didEnter or didExit delegate methods as appropriate.
    4. If the app is not running, it first launches the app into the background, calling the app delegate’s didFinishLaunching method. After 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.