You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义BLE设备反复出现在配对列表并尝试连接的iOS适配问题

Fixing BLE Reconnection Loops on Older iOS Devices

Hey there, let's tackle this annoying BLE reconnection issue you're seeing on older iOS devices (iPhone 6s iOS 11.2.1, iPhone 5s iOS 10.2.2). After diving into your BluetoothManager code, I've spotted several key gaps that are likely triggering the constant reconnection attempts, plus concrete fixes to resolve them.

What's Causing the Problem?

Let's walk through the main culprits in your code:

1. Unrestricted BLE Scanning

In centralManagerDidUpdateState, you're scanning for all BLE devices (using withServices: nil) with duplicate detections enabled:

central.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey:true])

Older iOS versions don't handle connected device filtering well—this means your app will keep detecting the same device even after it's connected, triggering the didDiscover method to retry the connection over and over.

2. Incomplete State Cleanup After Disconnect

  • Your didDisconnectPeripheral method only logs the disconnect, but doesn't clear the connectedDevice reference, stop pending send timers, or reset send state (segments, currentSegment). Older iOS systems hold onto stale peripheral references and automatically try to reconnect them.
  • The disconnect method doesn't null out the peripheral's delegate, leaving open callbacks that can trigger unintended reconnections.

3. Unmanaged Send Timers

You're using a repeating Timer to send segmented data, but there's no proper cleanup for it:

  • If the device disconnects or data finishes sending, the Timer might keep running and try to write to a disconnected peripheral—this triggers the system's automatic reconnection logic.
  • The Timer holds references to self and the peripheral, creating potential retain cycles and keeping the system thinking the device is active.

4. Aggressive Notification State Handling

In didUpdateNotificationStateFor, you immediately cancel the connection if notifications are disabled:

guard characteristic.isNotifying else {
    manager?.cancelPeripheralConnection(peripheral)
    return
}

On older iOS versions, this abrupt disconnect can trigger the system to try re-establishing the connection automatically.


Step-by-Step Fixes

Let's fix each issue one by one:

1. Restrict Scanning to Your Target Device

Scan only for your device's specific service UUID (replace YOUR_SERVICE_UUID with your actual service ID) and disable duplicate detections unless you need real-time RSSI updates:

func centralManagerDidUpdateState(_ central: CBCentralManager) {
    switch central.state {
    case .poweredOn:
        // Only scan for YOUR device's service, no duplicates
        let targetService = CBUUID(string: "YOUR_SERVICE_UUID")
        central.scanForPeripherals(withServices: [targetService], options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
    // ... keep other state handling
    }
}

2. Clean Up State Properly on Disconnect

Update didDisconnectPeripheral to clear all stale state:

func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
    print("OS Disconnect : \(String(describing: error?.localizedDescription))")
    
    // Clear state if this is our connected device
    if let connectedPeriph = connectedDevice?.peripheral, connectedPeriph == peripheral {
        connectedDevice = nil
        segments = nil
        currentSegment = 0
        sendTimer?.invalidate()
        sendTimer = nil
    }
}

And update disconnect to null out the peripheral's delegate and clean up all resources:

func disconnect() {
    completion = nil
    notFoundTimer?.invalidate()
    notFoundTimer = nil
    manager?.stopScan()
    
    guard let connectedDevice = self.connectedDevice else {
        return
    }
    
    // Null out delegate to stop callbacks
    connectedDevice.peripheral.delegate = nil
    connectedDevice.peripheral.setNotifyValue(false, for: connectedDevice.characteristic)
    manager?.cancelPeripheralConnection(connectedDevice.peripheral)
    
    // Clear all connection/send state
    self.connectedDevice = nil
    segments = nil
    currentSegment = 0
    sendTimer?.invalidate()
    sendTimer = nil
}

3. Manage Send Timer Lifecycle

Add a property to track the send Timer, and ensure it's stopped when needed:

class BluetoothManager:NSObject {
    // Add this property to track the send timer
    var sendTimer: Timer?
    // ... rest of your properties
}

Then update the timer creation and send logic:

func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
    if let error = error {
        self.completeWithResponse(response:.fail(error: error.localizedDescription))
        return
    }
    
    if !characteristic.isNotifying {
        // Clean up instead of abrupt disconnect
        connectedDevice = nil
        sendTimer?.invalidate()
        sendTimer = nil
        return
    }
    
    // Start send timer only if there's data to send
    guard let segments = segments, !segments.isEmpty else {
        return
    }
    sendTimer?.invalidate() // Stop any existing timer
    sendTimer = Timer.scheduledTimer(
        timeInterval: 0.05,
        target: self,
        selector: #selector(send(sender:)),
        userInfo: (peripheral: peripheral, characteristic: characteristic),
        repeats: true
    )
}

@objc internal func send(sender:Timer) {
    guard let userInfo = sender.userInfo as? (peripheral: CBPeripheral, characteristic: CBCharacteristic),
          let segments = segments,
          currentSegment < segments.count else {
        // Stop timer if no more data or invalid state
        sender.invalidate()
        sendTimer = nil
        return
    }
    
    userInfo.peripheral.writeValue(segments[currentSegment], for: userInfo.characteristic, type: .withoutResponse)
    currentSegment += 1
    
    // Stop timer once all segments are sent
    if currentSegment >= segments.count {
        sender.invalidate()
        sendTimer = nil
    }
}

4. Soften Notification State Handling

Instead of immediately canceling the connection when notifications are disabled, clean up state first. You can optionally trigger a reconnection if needed:

func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
    if let error = error {
        self.completeWithResponse(response:.fail(error: error.localizedDescription))
        return
    }
    
    if !characteristic.isNotifying {
        // Clean up state gracefully
        connectedDevice = nil
        sendTimer?.invalidate()
        sendTimer = nil
        // Optional: Trigger a reconnection if your app requires it
        // manager?.connect(peripheral, options: nil)
        return
    }
    
    // Proceed with sending data as before
    guard let segments = segments, !segments.isEmpty else {
        return
    }
    sendTimer?.invalidate()
    sendTimer = Timer.scheduledTimer(
        timeInterval: 0.05,
        target: self,
        selector: #selector(send(sender:)),
        userInfo: (peripheral: peripheral, characteristic: characteristic),
        repeats: true
    )
}

5. Add Older iOS Specific Checks

In iOS 10/11, the central manager can re-trigger scans unexpectedly. Add a check to only scan when there's no active connection:

func centralManagerDidUpdateState(_ central: CBCentralManager) {
    switch central.state {
    case .poweredOn:
        // Only scan if we don't have an active connection
        if connectedDevice == nil {
            let targetService = CBUUID(string: "YOUR_SERVICE_UUID")
            central.scanForPeripherals(withServices: [targetService], options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
        }
    // ... keep other state handling
    }
}

How to Verify the Fixes

  1. Test on the problematic devices (iPhone 5s iOS 10.2.1, iPhone 6s iOS 11.2.1) to confirm the reconnection loop stops.
  2. Use Xcode's Debug > Debug Workflow > View Bluetooth to monitor active connections—ensure no stale connections remain after disconnecting.
  3. Test the full flow: connect, send data, disconnect, and check that no automatic reconnection attempts happen.

内容的提问来源于stack exchange,提问作者Christos Chadjikyriacou

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:52:22