iOS BLE Deep Dive
Status: Pre-prototype reference document. iOS BLE background behavior is the #1 technical risk to this project. Read this before writing any iOS code.
1. The Core iOS BLE Problem
Why iOS Is Fundamentally Different from Android
On Android, an app can declare a foreground service and maintain a long-running background process with essentially unrestricted BLE scanning and advertising for as long as the service runs. The operating system does not systematically terminate these processes unless memory pressure forces it, and even then, services are typically restarted.
iOS operates on an entirely different philosophy. Apple's privacy model is built around the principle that apps should not run arbitrary background services. iOS aggressively manages process lifecycle: apps are suspended within seconds of moving to the background and are terminated when the system needs resources. There is no equivalent to Android's foreground service that allows a Bluetooth app to simply "keep running."
This is not a bug — it is intentional architecture. Apple's goal is to prevent battery drain, prevent user surveillance, and maintain a predictable security perimeter. For a privacy-first proximity app, the irony is that the platform with the strongest privacy branding is also the platform that makes privacy-preserving proximity detection hardest to build.
CBCentralManager in Background: What Actually Happens
CBCentralManager is the CoreBluetooth class for scanning — the "central" role that discovers advertising peripherals. When the app moves to the background without declaring any background modes, CBCentralManager delivers no scan results at all. The scanner is effectively frozen.
With the bluetooth-central UIBackgroundMode declared, CoreBluetooth does continue scanning in the background, but with a substantially reduced duty cycle. The exact duty cycle is not documented by Apple and varies by hardware and iOS version, but the practical effect is:
- Scan intervals are lengthened by the OS
- Not all advertisements are delivered — the system may batch or drop results
- Results may be delivered minutes after the advertisement was observed
- If the app is fully suspended (not just backgrounded), scan results accumulate in a system buffer and are delivered in a burst when the app next becomes active or wakes
With no bluetooth-central background mode declared, the app receives zero BLE events when backgrounded. This is absolute.
CBPeripheralManager in Background: What Actually Happens
CBPeripheralManager is the CoreBluetooth class for advertising — the "peripheral" role that broadcasts to nearby centrals. When backgrounded:
- With
bluetooth-peripheraldeclared: the device continues to advertise, but Apple strips the advertisement data. TheCBAdvertisementDataLocalNameKey(device name) is removed. The service UUIDs are moved to the "overflow area" — not the primary advertisement packet. - Without
bluetooth-peripheraldeclared: advertising stops entirely when the app is backgrounded.
The practical consequence is that a backgrounded iOS peripheral cannot be discovered by an Android central scanning for its service UUID in the normal advertisement payload. The UUID is only present in the overflow area, which only iOS devices know how to read.
The Overflow Area: iOS-to-iOS Only
Apple's overflow area is a proprietary mechanism introduced to work around their own advertisement stripping. When a backgrounded iOS peripheral cannot fit its service UUIDs into the normal advertisement packet (because the payload has been stripped), those UUIDs are encoded into a special manufacturer-specific data field that Apple calls the overflow area.
The overflow area encoding:
- Uses manufacturer-specific data with Apple's company ID (0x004C)
- Encodes a bitmask of service UUIDs that the app is advertising
- Is only decoded by
CBCentralManageron another iOS device - Is invisible to Android BLE scanners and to raw HCI-level inspection tools on non-Apple hardware
This means: a backgrounded iOS peripheral advertising via CoreBluetooth with bluetooth-peripheral declared is effectively invisible to Android. Only another iOS device running a CoreBluetooth central that knows to look in the overflow area can find it.
There is no workaround for this at the CoreBluetooth API level. It is a platform constraint.
Duty Cycle Reduction: The Numbers
Apple has never published the exact duty cycle used for background BLE scanning. Empirical data from developers and researchers suggests the following:
- Foreground: scan interval is approximately every 30–100ms (depends on parameters passed to
scanForPeripherals) - Background with
bluetooth-central: effective scan interval grows to several hundred milliseconds to several seconds - Screen-locked background: further duty cycle reduction; intervals of multiple seconds are common
- Low power mode active: additional reduction, potentially 10x longer intervals
For a passive contact-detection scenario (detect when a known contact comes within range), this means detection latency in the background can range from several seconds to over a minute, depending on conditions. This is still potentially acceptable for "was this person near me today" use cases, but not for real-time proximity alerts.
2. Per-Version Behavior Reference Table
The following documents observed and reported behavior. Apple does not publish BLE background behavior changelogs. Sources are developer reports, open-source project issue trackers, and Apple developer forum threads.
| iOS Version | Background Scan Results | Background Advertising | Time Before Callbacks Stop | Known Issues / Notes |
|---|---|---|---|---|
| iOS 15.x | Delivered with duty cycle reduction (~2–5s intervals observed). Reasonably reliable with bluetooth-central. |
Overflow area active. Service UUIDs stripped from primary packet. Local name stripped. | No hard time limit documented; delivery continues with increasing latency | Baseline behavior. Most BLE-background tutorials written against this era. Foreground-to-background transition has ~1–2s gap. |
| iOS 16.x | Behavior largely unchanged from iOS 15. Some reports of slightly improved reliability for scan delivery while screen-locked. | No documented change. Overflow area behavior unchanged. | No hard time limit reported | iOS 16.2+ introduced some Core Bluetooth stability fixes per release notes. No major regressions reported by BLE developers. |
| iOS 17.0–17.2 | Early iOS 17 reports of intermittent scan delivery failures in background. Some users reported CBCentralManager entering unknown state after extended background operation. | No documented change to overflow area. | iOS 17.0–17.1 had reports of callbacks stopping after ~10 minutes in certain conditions | iOS 17.0 introduced some background task scheduling changes. BLE-heavy apps (Berty, p2panda evaluations) reported increased instability. Partially addressed in 17.2. |
| iOS 17.3–17.6 | Stabilized. Background scan delivery considered comparable to iOS 15/16 baseline. | No change. | No hard limit; state restoration mechanism recommended as mitigation. | 17.4 introduced some privacy-related changes to background capability usage descriptions enforcement. App Store review more strict about declared background modes. |
| iOS 18.0–18.0.1 | Reports of regression: background scan delivery gaps of 30–60 seconds observed even with bluetooth-central declared. Connected devices (already paired GATT connections) appear unaffected. |
Initial reports of advertising instability — some apps seeing CBPeripheralManager not restart after system termination despite state restoration. |
Some reports of ~5 minute hard cutoff on scan event delivery for inactive (no active connections) scanning | iOS 18 introduced significant changes to background task execution. Multiple open-source BLE projects logged new issues. |
| iOS 18.1 | Partial improvement. Scan delivery gaps reduced but still higher latency than iOS 17.x baseline. | Advertising stabilized. | Less frequent hard cutoffs reported; ~10+ minutes before delivery stops without connections | Berty team and other BLE-focused developers noted iOS 18.1 as improvement over 18.0 but still not back to iOS 17 behavior. |
| iOS 18.2 | Further stabilization. Background scanning generally functional with bluetooth-central. Some developers report near-parity with iOS 17.6. |
No reported change. | No consistent hard limit reported in 18.2 | Multipeer Connectivity has a reported regression in iOS 18 specifically: reliable connections limited to approximately 2m. This is separate from CoreBluetooth. See Section 7. |
| iOS 19 (2026, projected) | No public beta at time of writing (April 2026). WWDC 2026 not yet held. No documented behavior changes. | Unknown. | Unknown. | Apple has shown interest in improving background execution for health and fitness apps (AirPods Pro, Apple Watch context). Some speculation that WWDC 2026 may introduce more permissive background BLE for specific entitlement categories. Unconfirmed. |
The iPhone 17 Device Regression (2026)
Separate from iOS version behavior, a hardware-specific regression has been reported on iPhone 17 devices (released September 2025) running iOS 18.x:
Reported behavior: Background BLE scanning stops delivering results immediately upon the app moving to the background, even when bluetooth-central is declared in UIBackgroundModes. The CBCentralManager does not enter an error state — it continues to report CBManagerStatePoweredOn — but centralManager(_:didDiscover:advertisementData:rssi:) is not called while the app is backgrounded.
Scope: Reports are specific to iPhone 17 (A18 Pro chip, new Bluetooth 5.4 hardware). iPhone 16 and iPhone 15 devices running the same iOS version do not show this behavior.
Current status (April 2026): Confirmed in multiple developer reports on Apple Developer Forums and in at least one open GitHub issue on a BLE-focused library. No Apple acknowledgment. No fix released. Workaround being explored: iBeacon monitoring appears unaffected (because it is handled at the OS level, not app level — see Section 5).
Impact on this project: If confirmed and not fixed, this makes CoreBluetooth background scanning unreliable on iPhone 17 hardware. The iBeacon monitoring fallback (Section 5) becomes more important, not optional.
3. CoreBluetooth Background Modes Explained
bluetooth-central in UIBackgroundModes
What it is: A key added to the UIBackgroundModes array in Info.plist that tells iOS your app uses CoreBluetooth in the central role and needs continued operation when backgrounded.
What it enables:
CBCentralManagercontinues scanning for peripherals when the app is backgrounded (with duty cycle reduction)CBCentralManagercontinues receiving data from connected peripherals when backgrounded- The app can be relaunched by the system when a BLE scan result arrives or a connected peripheral sends data (state restoration — see Section 4)
- Connection attempts in progress when the app moves to background are maintained
What it does NOT enable:
- Unrestricted continuous scanning (duty cycle is still reduced)
- Reliable delivery of all scan results (system may batch or drop)
- Persistence of the CBCentralManager state across app termination without state restoration keys
- Any guarantee of timely delivery
What happens without it:
- Scanning stops entirely when the app leaves the foreground
- No BLE events are delivered while backgrounded
- The delegate receives no callbacks
bluetooth-peripheral in UIBackgroundModes
What it is: The equivalent key for the peripheral role — apps that advertise to other devices.
What it enables:
CBPeripheralManagercontinues advertising when the app is backgrounded- Connected centrals continue to be able to read characteristics and receive notifications
- The app can be relaunched when a connected central accesses a characteristic
What it does NOT enable:
- Normal advertisement packet content — the service UUID is stripped to the overflow area
- Discovery by Android or non-Apple BLE centrals (they cannot read the overflow area)
- Local name in the advertisement
- Manufacturer-specific data in the primary advertisement packet (this is stripped too)
What happens without it:
- Advertising stops entirely when the app leaves the foreground
- Connected centrals lose connection immediately
Declaring Both Background Modes in Info.plist
An app needing both roles (which is typical — you want to both discover others and be discoverable) must declare both:
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>bluetooth-peripheral</string>
</array>
This goes in Info.plist. In Xcode, this is also configurable via the "Signing & Capabilities" tab → "Background Modes" capability → check both "Uses Bluetooth LE accessories" (central) and "Acts as a Bluetooth LE accessory" (peripheral).
Note: "Uses Bluetooth LE accessories" in Xcode's UI maps to bluetooth-central. The naming is confusing because it implies accessory communication, but this is the key for any background central-role scanning.
NSBluetoothAlwaysUsageDescription
Required since iOS 13 for any app using CoreBluetooth (both central and peripheral). If this key is absent from Info.plist, the app crashes when attempting to initialize CBCentralManager or CBPeripheralManager on iOS 13+.
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to discover nearby contacts you have previously met in person. No location information is transmitted.</string>
App Review requirements:
- The string must accurately describe why the app uses Bluetooth
- Vague strings like "app uses Bluetooth" are likely to be rejected
- Apple reviewers check that the declared usage matches the actual behavior in review
- If background modes are declared, the description should explain why background Bluetooth is needed
- Apps have been rejected for declaring
bluetooth-centralorbluetooth-peripheralbackground modes without sufficient justification in the review notes
What Apple looks for in App Review when background modes are declared:
- The usage description must explicitly mention why background Bluetooth is needed — not just that the app uses Bluetooth
- Review notes (the "Notes for Review" field in App Store Connect) should explain the use case: "The app discovers nearby contacts who have previously met the user in person. Background mode allows this discovery to work when the user is not actively using the app, for example when both users have their phones in their pockets."
- The app should actually use the declared capability. An app that declares background modes but only uses Bluetooth in the foreground will likely be flagged
- Apple has increased scrutiny of background mode declarations since iOS 17. Apps that have passed review in the past have been flagged upon update submission
4. State Preservation and Restoration
State preservation and restoration is CoreBluetooth's primary mechanism for surviving app termination while still reacting to BLE events. Without it, a terminated app that had active scans or connections will lose all that state and never receive BLE callbacks until manually relaunched by the user.
How It Works
When state preservation keys are provided at CBCentralManager or CBPeripheralManager initialization, iOS takes on the responsibility of tracking that manager's state. If the app is terminated (by the system due to memory pressure, or by the user via the app switcher), iOS preserves:
- The list of services being scanned for
- Any active or pending connections
- Connected peripheral state
- For peripherals: the GATT services and characteristics that were published
When a relevant BLE event occurs (a scan result matching a preserved UUID, a connection state change, a characteristic notification), iOS relaunches the app in the background with a short execution window (typically 10 seconds, extendable once via beginBackgroundTask(expirationHandler:)). The app is launched directly into application(_:didFinishLaunchingWithOptions:) with launch options indicating it was relaunched for Bluetooth.
Keys to Set
For Central:
let centralManager = CBCentralManager(
delegate: self,
queue: nil,
options: [CBCentralManagerOptionRestoreIdentifierKey: "com.yourapp.central"]
)
For Peripheral:
let peripheralManager = CBPeripheralManager(
delegate: self,
queue: nil,
options: [CBPeripheralManagerOptionRestoreIdentifierKey: "com.yourapp.peripheral"]
)
The restore identifier string is arbitrary but must be unique within your app. It is used by the system to match the preserved state to the new manager instance upon relaunch.
The willRestoreState Delegate Method
Upon relaunch, before centralManagerDidUpdateState is called, the delegate receives:
func centralManager(
_ central: CBCentralManager,
willRestoreState dict: [String: Any]
) {
// dict[CBCentralManagerRestoredStatePeripheralsKey]: [CBPeripheral]
// Previously connected or connecting peripherals
let restoredPeripherals = dict[CBCentralManagerRestoredStatePeripheralsKey]
as? [CBPeripheral] ?? []
// dict[CBCentralManagerRestoredStateScanServicesKey]: [CBUUID]
// Service UUIDs that were being scanned for
let restoredScanServices = dict[CBCentralManagerRestoredStateScanServicesKey]
as? [CBUUID] ?? []
// dict[CBCentralManagerRestoredStateScanOptionsKey]: [String: Any]
// Options that were passed to scanForPeripherals
let restoredScanOptions = dict[CBCentralManagerRestoredStateScanOptionsKey]
as? [String: Any] ?? [:]
// Reconnect to previously connected peripherals
for peripheral in restoredPeripherals {
peripheral.delegate = self
central.connect(peripheral, options: nil)
}
// Store restored scan info — re-scan after state is powered on
self.pendingScanServices = restoredScanServices
self.pendingScanOptions = restoredScanOptions
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
guard central.state == .poweredOn else { return }
// Resume scanning if we had a pending restore
if let services = pendingScanServices {
central.scanForPeripherals(
withServices: services,
options: pendingScanOptions
)
pendingScanServices = nil
}
}
For the peripheral role:
func peripheralManager(
_ peripheral: CBPeripheralManager,
willRestoreState dict: [String: Any]
) {
// dict[CBPeripheralManagerRestoredStateServicesKey]: [CBMutableService]
let restoredServices = dict[CBPeripheralManagerRestoredStateServicesKey]
as? [CBMutableService] ?? []
// dict[CBPeripheralManagerRestoredStateAdvertisementDataKey]: [String: Any]
let restoredAdvertisement = dict[CBPeripheralManagerRestoredStateAdvertisementDataKey]
as? [String: Any] ?? [:]
// Restore service references and re-add if not already present
self.pendingServices = restoredServices
self.pendingAdvertisement = restoredAdvertisement
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
guard peripheral.state == .poweredOn else { return }
for service in pendingServices {
peripheral.add(service)
}
if !pendingAdvertisement.isEmpty {
peripheral.startAdvertising(pendingAdvertisement)
}
}
Detecting Background Relaunch in AppDelegate
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let bluetoothLaunch = launchOptions?[.bluetooth] != nil
let peripheralLaunch = launchOptions?[.bluetoothPeripheral] != nil
if bluetoothLaunch || peripheralLaunch {
// App was relaunched by the system for a BLE event
// Do minimal work — execution window is short (~10 seconds)
// Initialize CBCentralManager / CBPeripheralManager with restore keys
// willRestoreState will be called immediately after init
initializeBluetoothManagers()
} else {
// Normal launch — initialize as usual
initializeBluetoothManagers()
}
return true
}
Critical Pitfalls
Execution window is short: When relaunched in the background, you have approximately 10 seconds of execution time. Use beginBackgroundTask(expirationHandler:) to request an extension (up to ~30 seconds on most devices), but this is not guaranteed. Do not attempt to show UI, perform network requests, or do heavy computation in this window. Only reinitialize CoreBluetooth managers, process the incoming BLE event, and persist any state to disk.
No guarantee of delivery: State restoration does not guarantee that the app will be relaunched for every BLE event. The system makes a best-effort attempt. If the system is under memory pressure or the phone is in a deep power-saving state, relaunch may not occur.
User can break state restoration: If the user manually terminates the app from the app switcher (swipe up to quit), state restoration is disabled. iOS treats a user-initiated termination as an explicit signal that the app should not relaunch. There is no API workaround for this.
Restoration vs. relaunch is not the same: State restoration means the CoreBluetooth framework restores manager state. It does not mean the app gets to run arbitrarily. The relaunch is into a background execution context, not a foreground context.
Events that trigger relaunch:
- A peripheral matching a preserved scan UUID is discovered
- A previously connected peripheral becomes reachable
- A GATT characteristic that was subscribed to sends a notification
- A connection to a peripheral changes state (connected, disconnected)
Events that do NOT trigger relaunch:
- Time-based events (you cannot use state restoration as a periodic wake)
- Advertised data not matching a preserved scan UUID
- GATT reads (only notifications/indications trigger relaunch)
5. CoreLocation iBeacon Monitoring — The Best Workaround
How iBeacon Monitoring Works
iBeacon is an Apple-designed BLE advertisement profile that encodes a UUID (16 bytes), major value (2 bytes), and minor value (2 bytes) into the advertisement packet. The standard was introduced with iOS 7.
The key architectural insight: iBeacon monitoring is implemented at the OS level, not the app level. The iOS location subsystem (CoreLocation, not CoreBluetooth) is responsible for detecting iBeacons. This means the app does not need to be running at all for iOS to detect a beacon. The OS is always watching.
When a beacon matching a registered CLBeaconRegion is detected, CoreLocation will:
- Deliver a
didEnterRegioncallback if the app is in the foreground or background - Relaunch a terminated app into the background with a short execution window
- Deliver a
didExitRegioncallback when the beacon is no longer detected
The monitoring API:
let beaconUUID = UUID(uuidString: "YOUR-APP-WIDE-UUID-HERE")!
// Monitor for any beacon with this UUID (any major/minor)
let beaconRegion = CLBeaconRegion(
uuid: beaconUUID,
identifier: "com.yourapp.contactbeacon"
)
beaconRegion.notifyOnEntry = true
beaconRegion.notifyOnExit = true
beaconRegion.notifyEntryStateOnDisplay = true // Deliver on screen unlock
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.startMonitoring(for: beaconRegion)
Detection callbacks:
func locationManager(
_ manager: CLLocationManager,
didEnterRegion region: CLRegion
) {
guard let beaconRegion = region as? CLBeaconRegion else { return }
// A beacon with our app UUID is within range
// App may have been relaunched in background to deliver this
// Begin CoreBluetooth contact identification (foreground or short background window)
handleProximityDetected()
}
func locationManager(
_ manager: CLLocationManager,
didExitRegion region: CLRegion
) {
// Beacon is no longer detected
handleProximityLost()
}
Why This Works Differently from CoreBluetooth Background Scanning
When iBeacon monitoring is active, iOS itself is watching for the registered UUID using the Bluetooth hardware, regardless of whether your app is running. This is structurally different from CoreBluetooth background scanning, where your app's CBCentralManager is doing the scanning with a duty-cycle-reduced scan.
The OS-level monitoring is:
- Always active (no duty cycle reduction for the detection trigger itself)
- Not affected by app suspension or termination
- Not affected by user swiping away the app (this is the critical difference from state restoration)
- Capped at 20 monitored regions per app (but one region with just a UUID covers all users of your app)
Latency and Reliability
Detection latency: iBeacon region entry detection typically takes 5–30 seconds from when the beacon first appears. This is a hardware + firmware decision by Apple — the OS does not scan continuously for beacons (that would be too power-intensive), so there is inherent detection delay.
Exit detection latency: Exit detection is slower, typically 20–60 seconds after the beacon stops advertising. This is because the OS needs to verify the beacon is truly gone (not just momentarily occluded) before declaring exit.
Reliability in practice: iBeacon monitoring is the most reliable background detection mechanism on iOS. Projects like AirTag proximity detection (though not public API), retail proximity analytics, and museum guide apps have relied on this for years. It works.
App Relaunch for Terminated Apps
When a terminated app's beacon region is entered, iOS relaunches the app with:
- Launch options containing
UIApplication.LaunchOptionsKey.location - A ~10 second execution window (extendable once to ~30 seconds)
Detection in AppDelegate:
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
if launchOptions?[.location] != nil {
// Could be iBeacon OR geofence trigger
// Initialize CLLocationManager — didEnterRegion will fire immediately
locationManager = CLLocationManager()
locationManager.delegate = self
// The delegate will receive didEnterRegion momentarily
}
return true
}
Entitlements and Permissions Required
iBeacon monitoring requires CLLocationManager with Always authorization. This is the most invasive location permission on iOS — it allows the app to access location data at any time, even when not in use.
The permission flow:
- App must include
NSLocationWhenInUseUsageDescription(required for the initial prompt) - App must include
NSLocationAlwaysAndWhenInUseUsageDescription(required for Always permission) - User grants "When In Use" first (iOS does not allow jumping directly to "Always")
- App calls
requestAlwaysAuthorization()— iOS prompts to upgrade to "Always" - Without "Always" authorization,
startMonitoring(for:)will not deliver events to a backgrounded/terminated app
The Info.plist entries:
<key>NSLocationWhenInUseUsageDescription</key>
<string>Location permission is used to detect nearby contacts via Bluetooth proximity. No GPS data is collected or stored.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Background location permission allows the app to detect when a known contact is nearby, even when you are not actively using the app. Only Bluetooth signal presence is used — no GPS coordinates are recorded.</string>
The Privacy Tradeoff: Location Permission for BLE Detection
This is a genuine tension in the design. The app's stated goal is structural privacy and no PII collection. Requiring location permission — especially "Always" — triggers immediate user concern and App Store scrutiny.
The technical reality: iBeacon monitoring uses Bluetooth hardware, not GPS. The CLLocationManager APIs are used because Apple chose to route iBeacon through CoreLocation (for historical reasons related to geofencing parity). When you use startMonitoring(for:) with a CLBeaconRegion, the device:
- Does NOT record GPS coordinates
- Does NOT log cell tower triangulation data
- Does NOT use any network-based location service
- ONLY uses the Bluetooth radio to listen for a specific BLE advertisement UUID
The permission label says "Location" because that is how Apple classifies the API. The actual capability being used is Bluetooth detection.
Can we use this without collecting location data? Yes. The app can use startMonitoring(for:) and startRangingBeacons(satisfying:) without ever accessing manager.location, without requesting GPS fixes, and without any network calls. The CLLocationManager delegate only receives beacon-related callbacks (didEnterRegion, didExitRegion, didRange). No latitude/longitude is involved.
How to explain this to users: The permission dialog text must be honest. The recommended framing is:
"This app uses Bluetooth to detect when people you know are nearby. iOS requires location permission to use this Bluetooth feature. No GPS location is collected or stored — only the Bluetooth presence of your contacts."
This framing is honest, accurate, and has been used by other proximity apps that have passed App Review.
App Store Review implications: Declaring NSLocationAlwaysAndWhenInUseUsageDescription and using it for iBeacon monitoring is an accepted App Store pattern. Apps like visitor guides, asset tracking tools, and retail apps use this routinely. The review team will scrutinize whether the "Always" permission is actually needed — in review notes, explain: "We use iBeacon monitoring to detect nearby contacts when the app is backgrounded. This requires Always authorization per CoreLocation documentation."
UUID Strategy
The iBeacon UUID is not a user identifier — it is a filter key. All devices running your app advertise the same UUID. The UUID just tells iOS "this advertisement is from our app."
Stable per-app UUID: E20A39F4-73F5-4BC4-A12F-17D1AD07A961
(Generate once, never change, ship in the app)
Major value: Could encode app version or network shard (0–65535)
Minor value: Could encode a rotating short-term identifier (0–65535)
Privacy design with major/minor values:
major: set to 0 or a fixed value for all users. Not used for identification.minor: rotate every 15–60 minutes using a deterministic function keyed to the user's private key. Contacts who know the user's public key can predict the current minor value and confirm presence. Strangers observe a random-looking minor value.- This gives you: presence detection that contacts can recognize, but that is unlinkable across time periods to strangers.
Monitoring vs. Ranging
- Monitoring (
startMonitoring(for:)): region entry/exit events. Works in background and when app is terminated. Coarse detection (is the beacon in range? yes/no). Use this for presence detection. - Ranging (
startRangingBeacons(satisfying:)): continuous distance estimation using RSSI. Foreground only. Providesproximity(.immediate,.near,.far) and estimated accuracy in meters. Use this only for active foreground interaction.
For passive background contact detection, monitoring is correct. Ranging is only appropriate when the user has opened the app and is actively trying to determine "how close is this contact."
iOS 17+ Behavior
No documented breaking changes to iBeacon monitoring in iOS 17 or 18. The mechanism is considered stable by Apple. The iPhone 17 hardware regression (Section 2) does not appear to affect iBeacon monitoring because the monitoring is handled by the CoreLocation daemon, not by the app's CBCentralManager.
6. Berty's Approach — Reference Implementation
Background and Architecture
Berty is an open-source, privacy-first messaging app built on libp2p. Their transport layer (Wesh Network) is designed to work over multiple physical transports including BLE, Multipeer Connectivity, and internet relays. Their approach to iOS BLE background behavior is the most thoroughly documented in the open-source space and represents the current state of the art.
Three-Transport Strategy
Berty's approach for iOS separates the problem by device pair type:
iOS-to-iOS: Multipeer Connectivity (MPC) is preferred. MPC uses AWDL (Apple Wireless Direct Link) under the hood on iOS. For iOS-to-iOS within proximity, MPC is more reliable than CoreBluetooth because it gets better OS treatment and Apple engineers have explicitly said it is the right API for iOS peer-to-peer. However, MPC has its own background restrictions.
iOS-to-Android (cross-platform): Custom CoreBluetooth GATT service. Berty defines a specific GATT service UUID and characteristic layout that both platforms can speak. The Android side uses standard Android BLE APIs. The iOS side uses CoreBluetooth with both background modes declared.
iOS-to-iOS over internet relay: When BLE and MPC fail, messages are routed through an optional internet relay (Tor or a rendezvous server). This is a fallback, not the primary path.
gomobile Binding Strategy
Berty's core protocol logic is written in Go and compiled for iOS using gomobile bind. The Go layer implements the libp2p protocol stack. The iOS-specific transport adapters are written in Swift/Objective-C and bridge into the Go layer.
This architecture matters for BLE background behavior: the Go runtime continues running while the iOS app has any background execution time, but it is still subject to iOS's background execution limits. The gomobile approach does not grant additional background privileges — it is constrained by the same bluetooth-central/bluetooth-peripheral background modes as any other iOS app.
BLE GATT Service Structure for Cross-Platform
Berty's BLE GATT profile (from their public source and blog posts):
- A primary service with a custom UUID
- A "peer ID" characteristic: the local peer's libp2p peer ID, readable by any central
- A "writer" characteristic: write-without-response, used to send data to the peripheral
- An "acknowledgment" characteristic: notify, used to confirm received data
- MTU negotiation handled in the application layer
This is a standard bidirectional communication pattern over GATT: each device acts as both central and peripheral simultaneously (the "dual role" pattern), allowing full-duplex communication.
Known Issues Documented by Berty
From their public issue tracker and blog posts:
-
iOS background advertising is invisible to Android: Confirmed the overflow area issue. Their workaround: they use a rotating service UUID that is placed in the primary advertisement when in the foreground. When backgrounded, they accept that Android cannot see them and rely on Android devices to be the aggressors (Android scans and finds iOS only when iOS is in the foreground, or through iBeacon as a wake trigger).
-
MPC in iOS 18 regression: Berty documented reduced MPC reliability in iOS 18, with connections requiring devices to be within approximately 1–2 meters. They have noted potential migration to Network.framework.
-
Background scan delivery gaps: In their telemetry, they observe gaps of 1–3 minutes in background scan delivery under some conditions. Their mitigation is aggressive use of GATT connection persistence — once connected to a peer, they keep the connection alive as long as possible, because connection notifications (rather than new scan discoveries) are more reliably delivered in the background.
-
Terminated app relaunch unreliability: Berty uses state restoration but acknowledges that relaunch does not always occur. Their protocol is designed to be tolerant of missed events — message sync is opportunistic, not guaranteed-delivery.
What Berty's Approach Tells Us
The takeaway from Berty's years of work on this problem:
- There is no clean solution to background BLE on iOS. Every approach is a workaround with failure modes.
- Cross-platform background discovery (iOS peripheral visible to Android central while iOS is backgrounded) is effectively impossible with CoreBluetooth alone.
- The most reliable iOS background detection involves keeping an active GATT connection to a known contact rather than relying on fresh scan discovery.
- For initial contact detection (when a known contact comes into range for the first time in a session), the iBeacon mechanism is more reliable than CoreBluetooth background scanning.
- Accept that iOS will have degraded functionality compared to Android. Design the protocol to be tolerant of delayed or missed proximity events.
7. Apple Multipeer Connectivity — When to Use It
What MPC Provides
Multipeer Connectivity (MultipeerConnectivity.framework) abstracts over three transports:
- Infrastructure WiFi (when both devices are on the same network)
- Peer-to-peer WiFi (AWDL — Apple Wireless Direct Link)
- Bluetooth LE (as fallback for discovery when WiFi is not available)
The framework handles transport selection automatically, picking the highest-bandwidth available option. For two iOS devices near each other, it typically uses AWDL, which provides WiFi-like speeds over a direct device-to-device link.
API surface:
// Advertising
let advertiser = MCNearbyServiceAdvertiser(
peer: localPeerID,
discoveryInfo: ["version": "1"],
serviceType: "yourapp-svc" // max 15 chars, lowercase alphanumeric + hyphens
)
advertiser.delegate = self
advertiser.startAdvertisingPeer()
// Browsing
let browser = MCNearbyServiceBrowser(
peer: localPeerID,
serviceType: "yourapp-svc"
)
browser.delegate = self
browser.startBrowsingForPeers()
What you get:
- Automatic discovery over BLE and WiFi simultaneously
- Automatic transport upgrade (starts on BLE, upgrades to WiFi if available)
- Built-in optional encryption (TLS over the session)
MCSessionfor bidirectional data transfer, streaming, and resource (file) sending- Simpler API than raw CoreBluetooth GATT
Why It Cannot Be Used for Cross-Platform
MPC is Apple-only and opaque. There is no public protocol specification. Android, Linux, or any non-Apple device cannot participate in an MPC session. The underlying AWDL protocol is proprietary, and while it has been reverse-engineered by researchers (the OWL project), those implementations are not production-ready and not suitable for a shipping app.
Additionally, MPC does not give you control over the advertisement payload. The discoveryInfo dictionary is visible to nearby browsers, but it must be treated as potentially readable by any iOS device running an app with the same service type string. You cannot embed cryptographic identifiers in the discovery info without custom encoding.
The iOS 18+ Stability Regression
Multiple developers and the Berty team have reported that Multipeer Connectivity in iOS 18 is significantly less stable than in iOS 17:
- Connection reliability: Connections that were reliable at 5–10 meters in iOS 17 now require devices to be within approximately 2 meters in iOS 18
- Disconnection frequency: Sessions disconnect more frequently during movement
- Reconnection: The browser does not always rediscover a peer after a session disconnects
Apple has acknowledged general MPC improvements as part of their recommendation to migrate to Network.framework for new development. Their documented stance is that MPC should be used for "prototyping and simple use cases" and that production apps should use Network.framework.
There is no public fix timeline for the iOS 18 regression. The regression appears to be in the AWDL layer, not in MPC's API surface.
Apple's Recommendation: Network.framework
Apple's current (2025–2026) official position is:
- Use
Network.frameworkwith peer-to-peer parameters for new development requiring device-to-device communication - MPC remains supported but is not the recommended path for new apps
- Network.framework provides more control, better observability, and better background behavior
See Section 8 for Network.framework details.
Tradeoff Table
| CoreBluetooth | Multipeer Connectivity | Network.framework P2P | |
|---|---|---|---|
| Cross-platform | Yes (with custom GATT) | No (Apple only) | Partial (mDNS is open) |
| Background discovery | Limited (duty cycle reduced) | Very limited | Better than MPC |
| Background data transfer | Yes (with GATT notify) | Unreliable in iOS 18 | Limited but more stable |
| Transfer speed | ~1 Mbps theoretical | Up to WiFi speeds (AWDL) | Up to WiFi speeds |
| Protocol control | Full | None | Full |
| Privacy | You control advertisement | discoveryInfo is cleartext | mDNS name is visible (opaque naming possible) |
| API complexity | High | Low | Medium |
| Stability (iOS 18) | Stable (with caveats) | Regressed | Stable |
| Use case fit | Passive contact discovery | iOS-to-iOS simple sync | iOS-to-iOS bulk transfer |
Recommendation: Use CoreBluetooth for discovery (cross-platform), Network.framework P2P for bulk transfer (iOS-to-iOS), and avoid MPC until the iOS 18 regression is resolved.
8. Network.framework with Peer-to-Peer WiFi
Overview
Network.framework is Apple's modern networking stack, introduced in iOS 12. It exposes NWBrowser for service discovery and NWListener for service advertising, with native support for peer-to-peer WiFi via Bonjour/mDNS.
Unlike CoreBluetooth, Network.framework peer-to-peer uses WiFi (AWDL for Apple-to-Apple, infrastructure WiFi or mDNS for broader discovery). This gives significantly higher bandwidth than BLE once a connection is established.
NWBrowser for Discovery
let parameters = NWParameters()
parameters.includePeerToPeer = true // enables AWDL + Bonjour
let browser = NWBrowser(
for: .bonjourWithTXTRecord(type: "_yourapp._tcp", domain: nil),
using: parameters
)
browser.browseResultsChangedHandler = { results, changes in
for change in changes {
switch change {
case .added(let result):
// New peer discovered
handleNewPeer(result)
case .removed(let result):
// Peer left
handlePeerLeft(result)
default:
break
}
}
}
browser.stateUpdateHandler = { state in
// Handle .ready, .failed, .cancelled
}
browser.start(queue: .main)
NWListener for Advertising
let parameters = NWParameters.tcp
parameters.includePeerToPeer = true
let listener = try? NWListener(using: parameters)
listener?.service = NWListener.Service(
name: deviceOpaqueName, // see privacy note below
type: "_yourapp._tcp"
)
listener?.newConnectionHandler = { connection in
// Incoming peer connection
handleIncomingConnection(connection)
}
listener?.stateUpdateHandler = { state in
// Handle .ready, .failed
}
listener?.start(queue: .main)
Background Behavior
Network.framework peer-to-peer has better background behavior than MPC but is still restricted:
- Foreground: Full discovery and connection capability
- Background (active connections): Established connections remain active; data transfer continues
- Background (no active connections): Discovery and listening are suspended; no new peers are found
- Terminated app: No passive discovery; app is not relaunched for peer discovery events (unlike iBeacon monitoring)
This means Network.framework is appropriate for bulk data transfer after a contact has been identified (via iBeacon monitoring or CoreBluetooth), but cannot serve as the passive background discovery layer.
Privacy Consideration: mDNS Service Names
When you advertise a service via NWListener.Service, the service name is broadcast over mDNS and is visible to any device on the network (or via AWDL peer-to-peer). If you use a persistent device identifier as the service name, this creates a tracking vector.
The wrong approach:
// BAD: stable device identifier in mDNS name
listener?.service = NWListener.Service(
name: "user-alice-device-abc123",
type: "_yourapp._tcp"
)
The right approach:
// GOOD: rotating opaque name derived from a time-windowed commitment
let opaqueEpoch = currentTimeEpoch() // changes every 15 minutes
let rotatingName = HMAC-SHA256(
key: devicePrivateSecret,
data: "mDNS-name-\(opaqueEpoch)"
).prefix(16).hexString
listener?.service = NWListener.Service(
name: rotatingName,
type: "_yourapp._tcp"
)
The type field (_yourapp._tcp) is not secret — it identifies the app. But the name field can be opaque and rotating, preventing device tracking via mDNS.
Cross-Platform Potential
mDNS (Bonjour) is an open standard (RFC 6762, RFC 6763). Android supports mDNS via NsdManager (Android 4.1+). This means that in theory, a device advertising via NWListener on iOS can be discovered by an Android device using NsdManager.discoverServices().
In practice, cross-platform mDNS over peer-to-peer WiFi (AWDL) has limitations:
- AWDL is Apple-proprietary and is not accessible from Android
- Infrastructure WiFi mDNS works cross-platform, but requires both devices to be on the same WiFi network
- Wi-Fi Direct on Android does not use mDNS in the same way
Realistic cross-platform scenario: Both devices on the same WiFi network → mDNS cross-platform discovery works. Over AWDL (device-to-device, no WiFi network) → iOS-only.
For our offline-first use case (no internet required), cross-platform Network.framework discovery over AWDL is not viable. CoreBluetooth GATT remains the cross-platform discovery mechanism.
Comparison: Network.framework vs. BLE for This Use Case
| Consideration | CoreBluetooth BLE | Network.framework P2P |
|---|---|---|
| Passive background discovery | Possible (limited) | Not possible for new connections |
| Cross-platform | Yes | iOS-only (AWDL) or same-network WiFi |
| Initial contact detection | Yes (beacon/GATT) | No |
| Bulk data sync after contact | Limited (~1 Mbps theoretical) | Yes (WiFi speeds) |
| Power consumption | Low (BLE designed for this) | Higher (WiFi radio) |
| Range | 10–100m | 10–100m (AWDL, similar to WiFi) |
| Protocol control | Full | Full |
Conclusion: Use CoreBluetooth for discovery and initial contact identification. Use Network.framework P2P (or WiFi Direct on Android) for bulk message/data sync once contact has been established.
9. Practical Recommendations
For the Prototype Phase
Should this app be Android-first?
Yes. The recommendation is to build and validate the core protocol on Android first, for the following reasons:
-
Android has no meaningful background BLE restrictions. A foreground service can scan and advertise continuously and indefinitely. This means you can test the actual protocol logic — key exchange, contact recognition, message sync — without fighting the platform.
-
The iOS BLE constraints are a delivery risk, not a design risk. The protocol design itself (BLE GATT for discovery, cryptographic identity, offline-first sync) is sound. iOS's constraints affect how reliably the protocol can operate on that platform, not whether the protocol design is correct.
-
An Android prototype proves the concept. A working Android-to-Android proximity contact exchange demonstrates the product to stakeholders, validates the UX, and gives you a reference implementation to port from.
-
iOS work is specialized and slow. Working around iOS BLE constraints (state restoration, iBeacon integration, permission flows) is iOS-specific engineering that adds significant complexity. Do this after the core protocol is stable.
Minimum viable iOS support: What can realistically be promised?
- Foreground: Full functionality. BLE discovery, NFC/QR contact exchange, message sync. No caveats.
- Background (app backgrounded, screen on): iBeacon monitoring triggers presence detection with 5–30 second latency. CoreBluetooth background scanning delivers results with duty-cycle reduction. Reasonable functionality.
- Background (screen locked): iBeacon monitoring continues (OS-level, unaffected by screen state). CoreBluetooth background scanning continues with increased latency. Functional but slower.
- Terminated app: iBeacon monitoring continues (OS-level relaunch). CoreBluetooth state restoration may or may not deliver events. Partial functionality.
- iPhone 17 devices (current regression): Background CoreBluetooth scanning does not work. iBeacon monitoring is the only reliable background detection. Reduced functionality acknowledged.
The honest user-facing statement: "Background contact detection works on iOS when you have granted location permission. Detection may take 30–60 seconds. App must not be force-quit by the user."
Which Workaround to Use
Primary recommendation: iBeacon monitoring + state restoration
Use both in combination:
- iBeacon monitoring as the wake trigger (reliable, works for terminated apps, survives user backgrounding)
- State restoration as supplementary for active GATT connections
The iBeacon approach requires "Always" location permission. This is the cost of reliable iOS background detection. Do not hide this from users — explain it clearly.
If location permission is unacceptable: State restoration with CoreBluetooth background modes only. Expect degraded reliability. Background detection will not work after app termination. Detection latency will be higher. This is a worse user experience but avoids the location permission requirement.
Do not rely on Multipeer Connectivity: The iOS 18 regression makes it unreliable at typical social distances. Until Apple resolves this, MPC should not be in the critical path.
Permission Strategy
Step 1: Bluetooth permission (required, no workaround)
Show a pre-permission dialog before the system prompt:
"This app uses Bluetooth to detect when contacts you've met in person are nearby. Tap Continue to grant Bluetooth access."
Then request: CBCentralManager initialization triggers the Bluetooth system prompt.
Step 2: Location permission (required for iBeacon background detection)
Do not ask for location permission at launch. Ask only when the user explicitly enables "background contact detection" in settings:
Pre-permission dialog:
"To detect nearby contacts when the app is in the background, iOS requires Location permission. This app uses only Bluetooth proximity detection — no GPS data is ever collected or stored. Tap Enable to grant this permission."
Then call requestAlwaysAuthorization().
If user declines location permission: The app still works in the foreground. Show a persistent settings banner: "Background detection is disabled. Grant Location permission in Settings to enable it."
"Reduced functionality on iOS" mode: Yes, implement this as a first-class concept. The app should have a settings toggle "Background contact detection" that is labeled "Requires Location permission on iOS." Users who are privacy-sensitive and do not want to grant location permission should get a clean foreground-only experience, not a broken app.
Testing Methodology
Devices required for comprehensive testing:
- Minimum: 2 iOS devices (different models if possible, one should be iPhone 15 or 16 for baseline, one iPhone 17 for regression testing)
- Ideal: 2 iOS devices + 1 Android device for cross-platform testing
Critical test scenarios (test each systematically):
| Scenario | Expected behavior | How to test |
|---|---|---|
| Both devices in foreground | Immediate mutual discovery | Standard use |
| One device backgrounded | iBeacon monitoring: 5–30s detection. CoreBluetooth: delivery with latency | Send backgrounded device to home screen, start timer |
| Both devices backgrounded | iBeacon monitoring on both: ~30s detection | Both home screen, verify callbacks via debug log |
| Backgrounded + screen locked | Same as backgrounded | Lock screen, verify callbacks |
| App terminated by system | iBeacon: relaunch + detect. CB: may not work | Simulate memory pressure or use Xcode to terminate |
| App force-quit by user | iBeacon: does NOT work (OS disable). CB: does not work | Swipe app out of switcher; verify no detection |
| Low battery mode | All callbacks delayed further | Enable Low Power Mode in settings |
| iPhone 17 regression | Background CB scanning stops; iBeacon still works | Test on iPhone 17 with iOS 18.x |
Tools:
- LightBlue (iOS App Store): Scan for nearby BLE devices and advertisements. Use to verify your app's advertisement is visible
- nRF Connect (iOS + Android): Professional BLE scanner with GATT explorer. Essential for verifying advertisement payload, service UUIDs, and GATT characteristic layout
- Xcode Instruments → Core Bluetooth: Available in Instruments (Xcode 15+). Shows CoreBluetooth state machine transitions, scan events, and timing
- Xcode Console + OSLog: Add
os_logcalls to all CoreBluetooth delegate methods with timestamps. Review logs after each background test scenario - Xcode → Debug → Simulate Memory Warning: Tests state restoration after simulated system termination
- TestFlight: Required for realistic background testing. Background behavior on simulator is not accurate — always test on device. TestFlight builds behave identically to App Store builds for background mode purposes
Logging pattern for background testing:
import os.log
private let bleLog = Logger(subsystem: "com.yourapp", category: "BLE")
func centralManager(
_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String: Any],
rssi RSSI: NSNumber
) {
bleLog.info(
"didDiscover: \(peripheral.identifier) rssi=\(RSSI) " +
"appState=\(UIApplication.shared.applicationState.rawValue)"
)
}
The applicationState in the log helps distinguish foreground vs. background callbacks when reviewing logs after a test run.
10. Summary Decision Matrix
| Scenario | Recommended Mechanism | Background Works? | Cross-Platform? | Location Perm Needed? | Notes |
|---|---|---|---|---|---|
| iOS-to-iOS passive discovery (background) | iBeacon monitoring + CoreBluetooth state restoration | Yes — iBeacon is reliable; CB is supplementary | No (iOS only) | Yes (Always) | iBeacon is the reliable trigger; CB delivers detail |
| iOS-to-Android passive discovery (background) | CoreBluetooth GATT + iBeacon as wake trigger | Unreliable — iOS peripheral invisible to Android when backgrounded | Yes (when iOS is foreground or Android is aggressive scanner) | Yes (for iBeacon wake) | Android must be the scanner; iOS cannot advertise to Android while backgrounded |
| Initial contact exchange (foreground, first meeting) | CoreBluetooth GATT + NFC tap or QR code | N/A (foreground) | Yes | No | NFC/QR for key exchange; BLE for subsequent recognition |
| Known contact detected nearby (foreground) | CoreBluetooth scanning | N/A (foreground) | Yes | No | Full functionality in foreground on all platforms |
| Known contact detected nearby (background, iOS) | iBeacon monitoring → relaunch → CoreBluetooth connect | Yes (iBeacon reliable, 5–30s latency) | No (iBeacon is iOS advertising) | Yes | Best available iOS background detection |
| Bulk data sync after contact detected | Network.framework P2P WiFi (iOS-iOS) or WiFi Direct (cross-platform) | Limited (no new connections in background) | Partial (AWDL iOS-iOS; WiFi Direct cross-platform) | No | Initiate while in foreground; keep session alive if background needed |
| Message sync with known contact in range | GATT notify on established connection | Yes (notify works in background with bluetooth-central) |
Yes | No | Maintain GATT connection; notify is the most reliable background data path |
| iOS 17.x and earlier — general background | CoreBluetooth background modes + state restoration | Reasonably reliable | Yes (central scanning) | No (but iBeacon adds reliability) | iOS 17 is the most stable baseline |
| iPhone 17 device (2026 regression) | iBeacon monitoring only | Yes for iBeacon; No for CoreBluetooth background scan | No (iBeacon only, iOS advertising) | Yes | CoreBluetooth background scanning broken on this hardware |
| Low battery / power saving mode | iBeacon monitoring (most power-efficient background mechanism) | Yes (OS handles it efficiently) | No | Yes | BLE GATT scanning degrades further in low power mode; iBeacon less affected |
Appendix: Key References and Source Material
The following sources informed this document. Where specific behaviors are claimed, these are the primary evidence sources:
- Apple Developer Documentation: Core Bluetooth Programming Guide — Background processing chapter
- Apple Developer Documentation: CLLocationManager — startMonitoring(for:) — iBeacon monitoring
- Apple Developer Documentation: UIBackgroundModes — Background mode keys
- Berty Blog: Bluetooth Low Energy & Berty — Cross-platform BLE challenges and Berty's approach
- Apple Developer Forums: Multiple threads on
CBCentralManagerbackground behavior regressions (iOS 17, iOS 18) — developer reports on scan delivery gaps - Apple Developer Forums: iPhone 17 background scanning regression reports (2026) — multiple independent confirmations
- WWDC Sessions: Session 901 (Core Bluetooth updates), various years — Apple's documented changes to CoreBluetooth
- Network.framework Documentation: NWBrowser, NWListener — peer-to-peer parameters
- MultipeerConnectivity Documentation: MCNearbyServiceAdvertiser — background behavior notes
- Berty/Wesh source code: github.com/berty/wesh-network — iOS BLE transport implementation reference
- p2panda research: Cross-platform proximity transport evaluation notes
Last updated: April 2026. iOS 26 behavior documented below.
Updates (2025–2026)
iPhone 17 BLE Background Scanning: Still Unresolved
The iPhone 17 hardware regression documented in this page remains unresolved as of early 2026. An Apple engineer (WWDR Engineering / Core Technologies) acknowledged the issue in February 2026, requested sysdiagnose logs and Bluetooth diagnostic profiles, and confirmed that bug report FB20381425 remains under investigation. No software fix has been released through iOS 26 as of April 2026. Source: Apple Developer Forums — iPhone 17 bluetooth background scanning issue — primary thread documenting the regression and Apple's response.
Apple Formally Deprecates MultipeerConnectivity (March 2025)
In March 2025 Apple published a detailed migration guide titled "Moving from Multipeer Connectivity to Network Framework." This formalises what was signalled with iOS 18: MPC is functionally deprecated. Apple engineers enumerate eight structural deficiencies — poor throughput, no flow control, forced P2P Wi-Fi even when unwanted, PKI-only security, and known unfixed bugs. The guide recommends Network.framework as the active replacement, offering QUIC, WebSocket, TCP, and UDP transports, TLS-PSK (simpler to deploy in peer-to-peer contexts), opt-in P2P Wi-Fi, and Swift-first concurrency APIs. Source: Moving from Multipeer Connectivity to Network Framework — Apple Developer Forums.
Design implication: Any iOS proximity transport layer built on MPC should be treated as a temporary measure. The ~2 m connection range regression on iOS 18 MPC is an additional reason to accelerate migration to Network.framework for bulk transfer.
WWDC 2025: Wi-Fi Aware Framework (iOS 26)
⚠ Contradicts existing content: The page notes "iOS 19 (2026, projected) — No public beta at time of writing." iOS 19 was branded iOS 26 and introduced a native Wi-Fi Aware framework as the most significant proximity networking announcement at WWDC 2025.
Wi-Fi Aware (Wi-Fi Alliance NAN standard) enables direct device-to-device communication without a router, while simultaneously maintaining an active internet Wi-Fi connection. Key properties:
- Hardware requirement: iPhone 12 and later
- Pairing model: Uses
DeviceDiscoveryUI(app-to-app) orAccessorySetupKit, then connections are opened via Network.framework (wifiAwareparameters) - Performance modes: bulk (lower power, higher latency) or real-time (higher power, lower latency)
- Cross-platform caveat: iOS 26 ↔ Android interoperability requires Wi-Fi Aware Specification v4.0 pairing support on the Android side. As of mid-2025, only the Samsung Galaxy S25 is confirmed compatible; Pixel 9 and Xiaomi 14 series are not.
Sources: WWDC25 Session 228 — Supercharge device connectivity with Wi-Fi Aware, Apple Developer Forums — Wi-Fi Aware between iOS 26 and Android.
Design implication: Wi-Fi Aware is a strong future transport candidate for iOS-to-iOS bulk data sync once the install base reaches iPhone 12+ saturation. It does not solve the Android interoperability problem in the short term. BLE must remain the universal discovery layer; Wi-Fi Aware can serve as a high-throughput upgrade path for iOS peers that have completed a BLE-based handshake.
Network.framework: Swift Structured Concurrency APIs (iOS 26)
WWDC 2025 Session 250 introduced async/await-native connection and listener management for Network.framework. This reduces the boilerplate required for the BLE-to-Network.framework handoff pattern and makes state restoration integration cleaner. Source: WWDC25 Session 250 — Use structured concurrency with Network framework.
Berty / Wesh: Proximity Transport Stability Fix (January 2025)
Berty's v2.470.9 release (January 22, 2025) fixed a critical bug where IPFS stalled when proximity transports (BLE and MPC) were enabled simultaneously. This confirms that the three-transport strategy (MPC for iOS-iOS, GATT for iOS-Android, internet fallback) remains the active architecture in Wesh-based implementations, and that the interaction between BLE event loops and IPFS's content routing is a known fragility. Source: Berty GitHub Releases.