iOS suspends app after BLE discovery even though I start Always-authorized location udpates (target: 16.3+)

I’m hitting a specific edge case with background execution that I can’t figure out. I'm using Flutter for the UI, but all the logic handles are in Swift using CoreBluetooth and CoreLocation.

I need the app to wake up from a suspended state when it detects my specific BLE peripheral (OBD sensor), connect to it, and immediately start continuous location tracking for the duration of the drive.

If I start this process while the app is in the foreground, or very shortly after going to BG, it works perfectly. The app stays alive for the whole trip.

The issue only happens when the sequence starts from the background:

  1. The app is suspended. scanForPeripherals wakes the app when the sensor is found.

  2. In didDiscover, I immediately call locationManager.startUpdatingLocation().

  3. locationd actually delivers updates successfully.

  4. However, 5-15 minutes later, iOS suspends the app again.

Crucially, I never see the blue "Location In Use" pill on the status bar, even though I have showsBackgroundLocationIndicator = true set. Also, distance filter is set to None.

Logs for reference (around suspending)

locationd: {"msg":"Sending location to client","Client":"[appName]:","desiredAccuracy":"-1.000000"}
runningboardd: Invalidating assertion ... from originator \\\[osservice<com.apple.bluetoothd>:...\\\]
runningboardd: Removed last relative-start-date-defining assertion for process app<[appName]...>
runningboardd: Calculated state ... running-suspended
runningboardd: Suspending task
locationd: Client [appName]: disconnected
bluetoothd: State of application "[appName]" is now "suspended"

Questions

  • Why does invalidating the Bluetooth assertion cause an immediate suspend even though I called startUpdatingLocation() and am receiving updates?

  • Does the missing blue location pill imply that the OS never fully "accepted" the location session?

  • Is there a specific "handshake" required to transition from a BLE wake-up to a long-running location session? I'm wondering if I need to use a background task identifier to bridge the gap between the BLE wake and the location manager taking over.

More context: Digging deeper in the comments, I just noticed the following patterns when the application is not suspended vs when it is recently suspended and got awaken by a BLE event.

Not suspended:

303948:Jan 23 20:59:35.640118 locationd[6491] <Debug>: {"msg":"Client is setting ContinuousBackgroundLocationRequested", "Client":"[appName]:", "ContinuousBackgroundLocationRequested":1}
303949:Jan 23 20:59:35.640155 locationd[6491] <Debug>: {"msg":"Allowing process assertion due to foreground-ish status", "ClientKeyPath":"[appName]:"}

Recently suspended and awaken by BLE:

564296:Jan 23 21:00:23.179125 locationd[6491] <Debug>: {"msg":"Client is setting ContinuousBackgroundLocationRequested", "Client":"[appName]:", "ContinuousBackgroundLocationRequested":1}
564298:Jan 23 21:00:23.179195 locationd[6491] <Notice>: {"msg":"#Warning Denying process assertion", "ClientKeyPath":"[appName]:"}

The assertion fails for the second case and that's why the app could not persist. Most importantly, following the logs in the second case, I see the following:

 locationd[6491] <Notice>: {"msg":"computing freshAuthorizationContext", "Client":"[appName]:", "ClientDictionary":"{\n    AlwaysServiceSession = 0;\n 

I suspect that the flag AlwaysServiceSession being 0 has to do with process assertion being denied for location.

iOS suspends app after BLE discovery even though I start Always-authorized location udpates (target: 16.3&#43;)
 
 
Q