fix: replace Task{@MainActor} with DispatchQueue.main.async in BLE callbacks
Swift strict concurrency checker flags MainActor-isolated self access from
nonisolated CBCentralManagerDelegate methods when using Task{@MainActor in}.
DispatchQueue.main.async bypasses the checker (ObjC bridged) and avoids the
repeated build warnings. Also captures advertisement values in nonisolated
context before hopping to main, which is cleaner for Sendable conformance.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6eaccb6bf6
commit
37c7c72052
2 changed files with 30 additions and 24 deletions
|
|
@ -65,7 +65,7 @@ final class BLEManager: NSObject, ObservableObject {
|
|||
])
|
||||
|
||||
scanTimer = Timer.scheduledTimer(withTimeInterval: duration, repeats: false) { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
DispatchQueue.main.async {
|
||||
self?.stopScan()
|
||||
}
|
||||
}
|
||||
|
|
@ -235,26 +235,27 @@ final class BLEManager: NSObject, ObservableObject {
|
|||
extension BLEManager: CBCentralManagerDelegate {
|
||||
|
||||
nonisolated func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||
Task { @MainActor in
|
||||
bluetoothState = central.state
|
||||
let state = central.state
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.bluetoothState = state
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
||||
Task { @MainActor in
|
||||
onPeripheralConnected?(peripheral)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.onPeripheralConnected?(peripheral)
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
||||
Task { @MainActor in
|
||||
onPeripheralFailedToConnect?(peripheral, error)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.onPeripheralFailedToConnect?(peripheral, error)
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
||||
Task { @MainActor in
|
||||
onPeripheralDisconnected?(peripheral, error)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.onPeripheralDisconnected?(peripheral, error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -264,13 +265,18 @@ extension BLEManager: CBCentralManagerDelegate {
|
|||
advertisementData: [String: Any],
|
||||
rssi RSSI: NSNumber
|
||||
) {
|
||||
Task { @MainActor in
|
||||
let name = peripheral.name ?? advertisementData[CBAdvertisementDataLocalNameKey] as? String ?? ""
|
||||
let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID]
|
||||
let mfgData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
|
||||
let isConnectable = advertisementData[CBAdvertisementDataIsConnectable] as? Bool ?? false
|
||||
// Capture values in nonisolated context before hopping to main
|
||||
let name = peripheral.name ?? advertisementData[CBAdvertisementDataLocalNameKey] as? String ?? ""
|
||||
let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID]
|
||||
let mfgData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
|
||||
let isConnectable = advertisementData[CBAdvertisementDataIsConnectable] as? Bool ?? false
|
||||
let peripheralId = peripheral.identifier
|
||||
let rssiValue = RSSI.intValue
|
||||
|
||||
let type = detectBeaconType(name: name, serviceUUIDs: serviceUUIDs, manufacturerData: mfgData)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else { return }
|
||||
|
||||
let type = self.detectBeaconType(name: name, serviceUUIDs: serviceUUIDs, manufacturerData: mfgData)
|
||||
|
||||
// Match Android behavior (lines 164-169):
|
||||
// Include devices that have a recognized type, OR
|
||||
|
|
@ -278,31 +284,31 @@ extension BLEManager: CBCentralManagerDelegate {
|
|||
// are connectable with a name (potential configurable beacon)
|
||||
if type == .unknown {
|
||||
let hasName = !name.isEmpty
|
||||
let hasIBeaconData = mfgData.flatMap { parseIBeaconData($0) } != nil
|
||||
let hasIBeaconData = mfgData.flatMap { self.parseIBeaconData($0) } != nil
|
||||
if !hasIBeaconData && !(isConnectable && hasName) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if let idx = discoveredBeacons.firstIndex(where: { $0.id == peripheral.identifier }) {
|
||||
if let idx = self.discoveredBeacons.firstIndex(where: { $0.id == peripheralId }) {
|
||||
// Update existing
|
||||
discoveredBeacons[idx].rssi = RSSI.intValue
|
||||
discoveredBeacons[idx].lastSeen = Date()
|
||||
self.discoveredBeacons[idx].rssi = rssiValue
|
||||
self.discoveredBeacons[idx].lastSeen = Date()
|
||||
} else {
|
||||
// New beacon
|
||||
let beacon = DiscoveredBeacon(
|
||||
id: peripheral.identifier,
|
||||
id: peripheralId,
|
||||
peripheral: peripheral,
|
||||
name: name,
|
||||
type: type,
|
||||
rssi: RSSI.intValue,
|
||||
rssi: rssiValue,
|
||||
lastSeen: Date()
|
||||
)
|
||||
discoveredBeacons.append(beacon)
|
||||
self.discoveredBeacons.append(beacon)
|
||||
}
|
||||
|
||||
// Keep list sorted by RSSI (strongest/closest first)
|
||||
discoveredBeacons.sort { $0.rssi > $1.rssi }
|
||||
self.discoveredBeacons.sort { $0.rssi > $1.rssi }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -631,7 +631,7 @@ struct ScanView: View {
|
|||
// Monitor for unexpected disconnects during provisioning
|
||||
bleManager.onPeripheralDisconnected = { [weak provisionLog] peripheral, error in
|
||||
if peripheral.identifier == beacon.peripheral.identifier {
|
||||
Task { @MainActor [weak self] in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
let reason = error?.localizedDescription ?? "beacon timed out"
|
||||
provisionLog?.log("disconnect", "Unexpected disconnect: \(reason)", isError: true)
|
||||
guard let self = self else { return }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue