Skip to content

Commit 2c75ff4

Browse files
committed
fix(ios): Avoid "ghost" peripherals on iOS
Identified in don/cordova-plugin-ble-central#1039 Sometimes iOS will return a new CBPeripheral for an existing device. When this happens, the original device stops working. Now, the latest peripheral supplied by iOS is always attached to the existing device wrapper
1 parent 9bb35ad commit 2c75ff4

File tree

4 files changed

+50
-11
lines changed

4 files changed

+50
-11
lines changed

ios/Sources/BluetoothLe/Device.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ class Device: NSObject, CBPeripheralDelegate {
3737
return self.peripheral
3838
}
3939

40+
func updatePeripheral(_ newPeripheral: CBPeripheral) {
41+
guard newPeripheral.identifier == self.peripheral.identifier else {
42+
log("Warning: Attempted to update peripheral with different UUID")
43+
return
44+
}
45+
self.peripheral = newPeripheral
46+
self.peripheral.delegate = self
47+
}
48+
4049
func setOnConnected(
4150
_ connectionTimeout: Double,
4251
_ callback: @escaping Callback

ios/Sources/BluetoothLe/DeviceManager.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,11 @@ class DeviceManager: NSObject, CBCentralManagerDelegate {
181181
guard ScanFilterUtils.passesServiceDataFilter(advertisementData, filters: self.serviceDataFilters) else { return }
182182

183183
let deviceId = peripheral.identifier.uuidString
184-
let result = self.discoveredDevices.getOrInsert(key: deviceId) {
185-
Device(peripheral)
186-
}
184+
let result = self.discoveredDevices.getOrInsert(
185+
key: deviceId,
186+
create: { Device(peripheral) },
187+
update: { $0.updatePeripheral(peripheral) }
188+
)
187189
let device = result.value
188190
let isNew = result.wasInserted
189191

ios/Sources/BluetoothLe/Plugin.swift

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,11 @@ public class BluetoothLe: CAPPlugin, CAPBridgedPlugin {
173173
call.reject("Device not found.")
174174
return
175175
}
176-
let storedDevice = self.deviceMap.getOrInsert(key: device.getId()) { device }.value
176+
let storedDevice = self.deviceMap.getOrInsert(
177+
key: device.getId(),
178+
create: { device },
179+
update: { $0.updatePeripheral(device.getPeripheral()) }
180+
).value
177181
let bleDevice: BleDevice = self.getBleDevice(storedDevice)
178182
call.resolve(bleDevice)
179183
} else {
@@ -210,7 +214,11 @@ public class BluetoothLe: CAPPlugin, CAPBridgedPlugin {
210214
call.reject(message)
211215
}
212216
}, { (device, advertisementData, rssi) in
213-
let storedDevice = self.deviceMap.getOrInsert(key: device.getId()) { device }.value
217+
let storedDevice = self.deviceMap.getOrInsert(
218+
key: device.getId(),
219+
create: { device },
220+
update: { $0.updatePeripheral(device.getPeripheral()) }
221+
).value
214222
let data = self.getScanResult(storedDevice, advertisementData, rssi)
215223
self.notifyListeners("onScanResult", data: data)
216224
}
@@ -235,9 +243,11 @@ public class BluetoothLe: CAPPlugin, CAPBridgedPlugin {
235243
let peripherals = deviceManager.getDevices(deviceUUIDs)
236244
let bleDevices: [BleDevice] = peripherals.map({peripheral in
237245
let deviceId = peripheral.identifier.uuidString
238-
let device = self.deviceMap.getOrInsert(key: deviceId) {
239-
Device(peripheral)
240-
}.value
246+
let device = self.deviceMap.getOrInsert(
247+
key: deviceId,
248+
create: { Device(peripheral) },
249+
update: { $0.updatePeripheral(peripheral) }
250+
).value
241251
return self.getBleDevice(device)
242252
})
243253
call.resolve(["devices": bleDevices])
@@ -255,9 +265,11 @@ public class BluetoothLe: CAPPlugin, CAPBridgedPlugin {
255265
let peripherals = deviceManager.getConnectedDevices(serviceUUIDs)
256266
let bleDevices: [BleDevice] = peripherals.map({peripheral in
257267
let deviceId = peripheral.identifier.uuidString
258-
let device = self.deviceMap.getOrInsert(key: deviceId) {
259-
Device(peripheral)
260-
}.value
268+
let device = self.deviceMap.getOrInsert(
269+
key: deviceId,
270+
create: { Device(peripheral) },
271+
update: { $0.updatePeripheral(peripheral) }
272+
).value
261273
return self.getBleDevice(device)
262274
})
263275
call.resolve(["devices": bleDevices])

ios/Sources/BluetoothLe/ThreadSafeDictionary.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,20 @@ class ThreadSafeDictionary<K: Hashable, T> {
4242
return (newValue, true)
4343
}
4444
}
45+
46+
/// Atomically gets existing value (calling update on it) or inserts new value
47+
/// The create closure is only called if the key doesn't exist
48+
/// The update closure is called on existing values before returning
49+
/// Returns tuple of (value, wasInserted) where wasInserted indicates if a new value was created
50+
func getOrInsert(key: K, create: () -> T, update: (T) -> Void) -> (value: T, wasInserted: Bool) {
51+
return queue.sync(flags: .barrier) {
52+
if let existing = dictionary[key] {
53+
update(existing)
54+
return (existing, false)
55+
}
56+
let newValue = create()
57+
dictionary[key] = newValue
58+
return (newValue, true)
59+
}
60+
}
4561
}

0 commit comments

Comments
 (0)