iOS Swift中Semaphore使用崩溃求助:视图释放后回调崩溃
问题分析与解决方案
你的代码用信号量实现了异步任务顺序执行,但存在两个核心问题:
- 如果在主线程调用
semaphore.wait(),会直接阻塞主线程导致界面卡死,这本身就是错误用法; - 页面销毁后,回调仍持有已释放的对象(或信号量),触发时会引发崩溃——信号量本身没有“释放”概念,问题根源是任务生命周期未与页面绑定。
下面给出几种更安全的替代方案:
方案一:串行队列 + 链式异步调用
用串行队列天然的顺序执行特性,结合[weak self]避免循环引用,页面销毁后后续任务会自动终止。
// 定义私有串行队列 private let taskSerialQueue = DispatchQueue(label: "com.your.app.task.queue") // 启动任务链 func startSequentialTasks() { taskSerialQueue.async { [weak self] in self?.performFirstTask { self?.taskSerialQueue.async { self?.performSecondTask { self?.taskSerialQueue.async { self?.performThirdTask { // 所有任务完成后的逻辑 } } } } } } }
方案二:OperationQueue 管理任务(推荐)
通过设置最大并发数为1实现顺序执行,还支持主动取消任务,适配页面销毁场景。
第一步:自定义异步 Operation(处理异步任务)
系统默认的BlockOperation不支持异步任务,需要自定义:
class AsyncOperation: Operation { private enum State: String { case ready, executing, finished } private let asyncTask: (@escaping () -> Void) -> Void private var currentState: State = .ready { willSet { willChangeValue(forKey: newValue.rawValue) willChangeValue(forKey: currentState.rawValue) } didSet { didChangeValue(forKey: oldValue.rawValue) didChangeValue(forKey: currentState.rawValue) } } override var isReady: Bool { super.isReady && currentState == .ready } override var isExecuting: Bool { currentState == .executing } override var isFinished: Bool { currentState == .finished } override var isAsynchronous: Bool { true } init(task: @escaping (@escaping () -> Void) -> Void) { self.asyncTask = task super.init() } override func start() { guard !isCancelled else { currentState = .finished return } currentState = .executing asyncTask { [weak self] in guard let self = self, !self.isCancelled else { return } self.currentState = .finished } } }
第二步:配置任务队列与依赖
private let operationQueue = OperationQueue() func setupSequentialTasks() { operationQueue.maxConcurrentOperationCount = 1 // 创建三个异步任务 let firstTask = AsyncOperation { [weak self] completion in self?.performFirstTask { completion() } } let secondTask = AsyncOperation { [weak self] completion in self?.performSecondTask { completion() } } let thirdTask = AsyncOperation { [weak self] completion in self?.performThirdTask { completion() } } // 设置任务依赖:先执行first,再second,最后third secondTask.addDependency(firstTask) thirdTask.addDependency(secondTask) // 添加到队列执行 operationQueue.addOperations([firstTask, secondTask, thirdTask], waitUntilFinished: false) } // 页面销毁时取消所有未完成任务 deinit { operationQueue.cancelAllOperations() }
方案三:信号量的安全改造(不推荐)
如果一定要用信号量,必须在后台线程执行wait操作,同时在页面销毁时手动释放阻塞的线程:
private var semaphore: DispatchSemaphore? = DispatchSemaphore(value: 1) private let backgroundQueue = DispatchQueue(label: "com.your.app.semaphore.queue") func startTasksWithSemaphore() { backgroundQueue.async { [weak self] in guard let semaphore = self?.semaphore else { return } semaphore.wait() self?.performFirstTask { semaphore.signal() } semaphore.wait() self?.performSecondTask { semaphore.signal() } semaphore.wait() self?.performThirdTask { semaphore.signal() } } } // 页面销毁时释放所有可能的阻塞 deinit { // 最多3次wait,所以signal3次确保线程被释放 semaphore?.signal() semaphore?.signal() semaphore?.signal() semaphore = nil }
这种方式风险较高:无法准确判断当前阻塞的wait次数,容易出现信号量值异常,仅作为应急方案。
内容的提问来源于stack exchange,提问作者MobX
相关产品推荐
相关产品推荐

