Dispatch Semaphore内修改外部变量无效及async-await改造咨询
Dispatch Semaphore异步修改对象属性不生效问题解析与async-await优化方案
原问题代码的核心错误
你遇到的问题本质是信号量signal()的调用时机错误:saveImagesToFireBaseStorage是带闭包回调的异步操作,你在调用该函数后立刻执行了semaphore.signal(),导致主线程的semaphore.wait()在图片上传完成、闭包修改page属性之前就解除了等待。也就是说,当你执行到打印page.picURL1或后续保存步骤时,闭包里的page.picURL1 = url代码还未执行,自然拿到的是旧值。
举个执行顺序的例子:
- 进入
newQueue.async代码块 - 调用
saveImagesToFireBaseStorage(发起异步上传,函数立刻返回) - 执行
semaphore.signal(),主线程wait()结束并继续向下执行 - 一段时间后图片上传完成,闭包才执行并修改
page属性
此时主线程早已走到打印甚至保存逻辑,所以看到的都是未修改的旧值。
原代码的修复方案(基于Semaphore)
必须把semaphore.signal()移到异步闭包内部,确保只有当图片上传完成、属性修改完毕后才触发信号量;同时要处理无图片URL的情况,避免主线程永久等待:
func savePage(model: PageModel, savingHandler: @escaping (Bool) -> Void) { // some code ..... var page = model let newQueue = DispatchQueue(label: "image upload queue") let semaphore = DispatchSemaphore(value: 0) newQueue.async { if let picURL1 = model.picURL1 { self.saveImagesToFireBaseStorage(pictureURL: picURL1) { urlString in if let url = urlString { page.picURL1 = url } semaphore.signal() // 移到闭包内部,确保属性修改完成后发信号 } } else { semaphore.signal() // 无图片时也要发信号,避免主线程阻塞 } } semaphore.wait() newQueue.async { if let picURL2 = model.picURL2 { self.saveImagesToFireBaseStorage(pictureURL: picURL2) { urlString in if let url = urlString { page.picURL2 = url } semaphore.signal() } } else { semaphore.signal() } } semaphore.wait() print(page.picURL1 ?? "nil") // 现在能拿到正确值 newQueue.async { print(page.picURL1 ?? "nil") do { try pageDocumentRef.setData(from: page) savingHandler(true) } catch let error { print("Error writing city to Firestore: \(error)") savingHandler(false) // 错误场景也要回调外部 } semaphore.signal() } semaphore.wait() }
你的async-await方案点评与优化
改用async-await的思路非常正确,相比手动管理Semaphore,这种写法更简洁易读,也避免了信号量时机错误的坑。这里给几个优化细节:
1. 并行执行图片上传提高效率
原updatePageWithNewImageURLS里的三个await是串行执行的,若图片上传之间无依赖,可改用async let实现并行上传:
private func updatePageWithNewImageURLS(_ page : PageModel) async throws -> PageModel { // 同时发起三个上传请求 async let picUrl1 = saveImagesToFireBaseStorage(pictureURL: page.picURL1) async let picUrl2 = saveImagesToFireBaseStorage(pictureURL: page.picURL2) async let picUrl3 = saveImagesToFireBaseStorage(pictureURL: page.picURL3) // 等待所有请求完成 let url1 = await picUrl1 let url2 = await picUrl2 let url3 = await picUrl3 var newPage = page if let url1 = url1 { newPage.picURL1 = url1 } if let url2 = url2 { newPage.picURL2 = url2 } if let url3 = url3 { newPage.picURL3 = url3 } return newPage }
2. 简化无异步逻辑的函数
addNewUrlstoPage仅做同步属性修改,无需标记async throws,可简化为:
private func addNewUrlstoPage(_ page : PageModel, url1: String?, url2 : String?, url3 :String?) -> PageModel { var newPage = page if let url1 = url1 { newPage.picURL1 = url1 } if let url2 = url2 { newPage.picURL2 = url2 } if let url3 = url3 { newPage.picURL3 = url3 } return newPage }
3. 完善回调逻辑
在savePage的Task中,无论成功或失败都要调用savingHandler,避免外部永久等待:
func savePage(model: PageModel, savingHandler: @escaping (Bool) -> Void) { // Some code Task { do { let page = model let updatedPage = try await updatePageWithNewImageURLS(page) try pageDocumentRef.setData(from: updatedPage) savingHandler(true) // 成功回调 } catch { print("Error: \(error)") savingHandler(false) // 失败回调 } } // Some code }
内容的提问来源于stack exchange,提问作者incelemeTRe
相关产品推荐
相关产品推荐

