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

EXC_BAD_ACCESS崩溃求助:ConnectivityManager添加Operation异常排查

Firebase崩溃分析:EXC_BAD_ACCESS KERN_INVALID_ADDRESS 问题排查

问题背景

近几日在Firebase崩溃分析控制台发现EXC_BAD_ACCESS KERN_INVALID_ADDRESS类型崩溃,排查日志后仍未找到解决方案。不确定崩溃是否与调用addTransferOperation方法(代码line-1)或方法内部的添加Operation逻辑(代码line-2)有关,有两个疑问:

  1. 若问题与addOperation相关,该如何解决?
  2. transferOperation为何会出现nil的情况?

崩溃调用栈

Crashed: com.apple.main-thread
0  Foundation                     0x114ea8 ____addOperations_block_invoke.567 + 680
1  Foundation                     0x113858 __addOperations + 1288
2  GrandApp                      0x7bd3d8 ConnectivityManager.addTransferOperation(device:deviceIndex:dataTransferCategory:) + 2411 (ConnectivityManager.swift:2411)
3  GrandApp                      0x7bc3f0 ConnectivityManager.transferOperation(withDevices:notifyUser:isManualSync:) + 4313269232 (<compiler-generated>:4313269232)
4  GrandApp                      0x7a6770 ConnectivityManager.startTransferOperation(notifyUser:device:syncAllData:isBeaconFlow:) + 4313180016 (<compiler-generated>:4313180016)
5  GrandApp                      0x6c4aa4 closure #1 in DashboardViewController.refreshAction() + 1096 (DashboardViewController.swift:1096)
6  GrandApp                      0x7cc37c specialized ConnectivityManager.isSyncing(withStop:completion:) + 4313334652 (<compiler-generated>:4313334652)
7  GrandApp                      0x6c4218 DashboardViewController.refreshAction() + 4312252952 (<compiler-generated>:4312252952)
8  GrandApp                      0x6c4aec @objc DashboardViewController.refreshAction() + 4312255212 (<compiler-generated>:4312255212)
9  UIKitCore                      0x8be300 -[UIApplication sendAction:to:from:forEvent:] + 96
10 UIKitCore                      0x367424 -[UIControl sendAction:to:forEvent:] + 80
11 UIKitCore                      0x367744 -[UIControl _sendActionsForEvents:withEvent:] + 440
12 UIKitCore                      0x3667b0 -[UIControl touchesEnded:withEvent:] + 568
13 UIKitCore                      0x8f55c4 -[UIWindow _sendTouchesForEvent:] + 2108
14 UIKitCore                      0x8f67ec -[UIWindow sendEvent:] + 3140
15 UIKitCore                      0x8d685c -[UIApplication sendEvent:] + 340
16 UIKitCore                      0x99c9d4 __dispatchPreprocessedEventFromEventQueue + 1768
17 UIKitCore                      0x99f100 __handleEventQueueInternal + 4828
18 UIKitCore                      0x998330 __handleHIDEventFetcherDrain + 15
19 CoreFoundation                 0xaaf1c CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION + 24
20 CoreFoundation                 0xaae9c __CFRunLoopDoSource0 + 88
21 CoreFoundation                 0xaa784 __CFRunLoopDoSources0 + 176
22 CoreFoundation                 0xa56c0 __CFRunLoopRun + 1004
23 CoreFoundation                 0xa4fb4 CFRunLoopRunSpecific + 436
24 GraphicsServices               0xa79c GSEventRunModal + 104
25 UIKitCore                      0x8bcc38 UIApplicationMain + 212
26 GrandApp                      0x1db0c main + 19 (AppDelegate.swift:19)
27 libdyld.dylib                  0x18e0 start + 4

相关代码

line-1(调用处)

_ = self.addTransferOperation(device: device, deviceIndex:index, dataTransferCategory:OMVitalDataTransferCategory.bloodPressure)

line-2(addTransferOperation方法实现)

func addTransferOperation(device: ConnectedDevice, deviceIndex:Int, dataTransferCategory:OMVitalDataTransferCategory = OMVitalDataTransferCategory.all) -> ConnectivityTransferOperation {
    let transferOperation = ConnectivityTransferOperation()
    //            transferOperation.isUserNotify = device == sortedDeviceList.last ? notifyUser : false
    let connectedDevice = ConnectedDevice(value : device)
    transferOperation.device = ConnectedDevice(value : device)
    transferOperation.transferDataType = dataTransferCategory
    transferOperation.completionBlock = {
        LogManager.shared.addLog(logString: "completion of transfer operation",localName: connectedDevice.deviceLocalName, uuid: connectedDevice.uuid)
        // Remove first item
        if !self.pendingOperations.syncInProgress.isEmpty {
            self.pendingOperations.syncInProgress.removeAll(where: {$0 == transferOperation})
        }
        
        // Empty queue
        if self.pendingOperations.syncInProgress.isEmpty {
            // update autosync timer if Manual sync
            if (self.isManualSync) {
                self.updateSyncTimer()
            }
        }
        
        // Reset bluetooth state
        self.resetBluetoothState()
        
        DispatchQueue.main.async {
            // Set error from transfer operation and set flag to check if it happened in transfer operation
            let transferOperationDetails: [String : Any] = [ConnectivityConfiguration.error: transferOperation.error,
                                                     ConnectivityConfiguration.isTransferOperation:true]
            // post notification for stop data transfer with transfer error operation object
            NotificationCenter.default.post(name: .dataTransferStop,
                                            object: transferOperationDetails)
        }
    }
    
    LogManager.shared.addLog(logString: "Add transfer operation for device \(device.displayName)",localName: connectedDevice.deviceLocalName, uuid: connectedDevice.uuid)
    // Add operation and start
    self.pendingOperations.syncInProgress.append(transferOperation)
    if deviceIndex > 0 {
        // add dependency
        transferOperation.addDependency(self.pendingOperations.syncInProgress[deviceIndex-1])
    }
    self.pendingOperations.syncQueue.addOperation(transferOperation)
    
    return transferOperation
}

ConnectivityTransferOperation.swift

import UIKit
import OMConnectivityLibrary

/// Transfer operation for connectivity
class ConnectivityTransferOperation : ConnectivityOperation {
    
    override func main() {
        guard isCancelled == false else {
            finish(true)
            return
        }
        self.executing(true)
    }
    
    override func end() {
        // Super call
        super.end()
    }
}

ConnectivityOperation.swift

import UIKit
import OMConnectivityLibrary

class ConnectivityOperation: Operation {
    
    /// Making operation queue async
    override var isAsynchronous: Bool {
        get {
            return  true
        }
    }
    
    private var _executing = false {
        willSet {
            willChangeValue(forKey: "isExecuting")
        }
        didSet {
            didChangeValue(forKey: "isExecuting")
        }
    }
    
    private var _finished = false {
        willSet {
            willChangeValue(forKey: "isFinished")
        }
        didSet {
            didChangeValue(forKey: "isFinished")
        }
    }
    
    override var isExecuting: Bool {
        return _executing
    }
    
    override var isFinished: Bool {
        return _finished
    }
    
    func executing(_ executing: Bool) {
        _executing = executing
    }
    
    func finish(_ finished: Bool) {
        _finished = finished
    }
    
    func end() {
        self.finish(true)
        self.executing(false)
    }
}

问题分析与解决方案

1. 崩溃原因定位

从调用栈看,崩溃发生在Foundation的____addOperations_block_invoke.567,对应代码中self.pendingOperations.syncQueue.addOperation(transferOperation)这一行。结合EXC_BAD_ACCESS类型,大概率是内存访问问题:要么是transferOperation状态管理异常被提前释放,要么是syncQueue本身失效,或者自定义Operation的状态逻辑不符合Apple规范,导致队列操作时访问无效内存。

2. transferOperation出现nil的可能性

你的代码中transferOperation是直接初始化的let transferOperation = ConnectivityTransferOperation(),正常情况下不可能为nil。但存在以下间接导致nil访问的场景:

  • ConnectedDevice(value: device)初始化失败,导致transferOperation.device为nil,后续操作访问该属性时崩溃(但调用栈显示崩溃在addOperation阶段,此可能性较低);
  • 自定义Operation的状态管理错误,导致对象内部状态混乱,被队列视为已释放对象;
  • pendingOperations.syncInProgress数组在多线程环境下被修改,添加依赖时访问了无效元素。

3. 针对addOperation相关问题的解决方案

(1)修复自定义Operation的状态管理

Apple对异步Operation的状态管理有严格要求,你的实现存在状态转换不完整的问题:

  • main()方法只设置了executing(true),未在操作完成时调用end(),导致isFinished一直为false,队列无法正确管理生命周期;
  • 取消操作的状态处理不完整;
  • 缺少线程安全锁,多线程下状态可能混乱。

修改后的ConnectivityOperation:

class ConnectivityOperation: Operation {
    override var isAsynchronous: Bool { true }
    
    private let lock = NSLock()
    private var _executing = false
    private var _finished = false
    
    override var isExecuting: Bool {
        lock.lock()
        defer { lock.unlock() }
        return _executing
    }
    
    override var isFinished: Bool {
        lock.lock()
        defer { lock.unlock() }
        return _finished
    }
    
    func startExecuting() {
        willChangeValue(forKey: "isExecuting")
        lock.lock()
        _executing = true
        lock.unlock()
        didChangeValue(forKey: "isExecuting")
    }
    
    func finishOperation() {
        willChangeValue(forKey: "isExecuting")
        willChangeValue(forKey: "isFinished")
        lock.lock()
        _executing = false
        _finished = true
        lock.unlock()
        didChangeValue(forKey: "isExecuting")
        didChangeValue(forKey: "isFinished")
    }
    
    override func cancel() {
        super.cancel()
        if !isFinished {
            finishOperation()
        }
    }
    
    override func start() {
        if isCancelled {
            finishOperation()
            return
        }
        startExecuting()
        main()
    }
}

对应的ConnectivityTransferOperation修改:

class ConnectivityTransferOperation : ConnectivityOperation {
    override func main() {
        guard !isCancelled else {
            finishOperation()
            return
        }
        // 执行实际传输逻辑后,必须调用finishOperation()
        // 例:蓝牙传输完成后调用
        // finishOperation()
    }
}

注意:操作完成或取消时必须调用finishOperation(),否则队列会一直持有该对象,引发内存泄漏或状态混乱。

(2)确保线程安全

pendingOperations.syncInProgress数组和syncQueue的操作需保证线程安全:

  • 对syncInProgress的读写操作加锁,避免多线程同时修改导致数组越界;
  • 添加安全下标访问,防止依赖操作时数组越界。

修改addTransferOperation中的数组操作:

// 在ConnectivityManager中定义锁对象
private let operationLock = NSLock()

// 修改数组操作部分
operationLock.lock()
self.pendingOperations.syncInProgress.append(transferOperation)
var dependencyOp: ConnectivityTransferOperation? = nil
if deviceIndex > 0 {
    dependencyOp = self.pendingOperations.syncInProgress[safe: deviceIndex-1]
}
operationLock.unlock()

if let op = dependencyOp {
    transferOperation.addDependency(op)
}

self.pendingOperations.syncQueue.addOperation(transferOperation)

给数组添加安全下标扩展:

extension Collection {
    subscript(safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

(3)修复completionBlock循环引用

completionBlock中捕获self和transferOperation会导致循环引用,无法释放对象,需使用弱引用:

transferOperation.completionBlock = { [weak self, weak transferOperation] in
    guard let self = self, let transferOperation = transferOperation else { return }
    // 原逻辑代码
}

4. 额外排查点

  • 检查ConnectedDevice.init(value:)是否存在线程安全问题,或是否为可选初始化可能返回nil;
  • 确认syncQueue配置正确,未被意外释放;
  • 在Firebase崩溃报告中查看更多上下文(如寄存器值),定位具体无效内存对应的对象。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 16:36:10