EXC_BAD_ACCESS崩溃求助:ConnectivityManager添加Operation异常排查
问题背景
近几日在Firebase崩溃分析控制台发现EXC_BAD_ACCESS KERN_INVALID_ADDRESS类型崩溃,排查日志后仍未找到解决方案。不确定崩溃是否与调用addTransferOperation方法(代码line-1)或方法内部的添加Operation逻辑(代码line-2)有关,有两个疑问:
- 若问题与
addOperation相关,该如何解决? 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

