Swift中如何让Alamofire响应返回至Core Data上下文所在同一后台线程
核心问题解决方法
你遇到的线程不匹配问题的本质是Core Data的托管对象上下文(MOC)只能在自身绑定的专属队列中操作,你不需要强制网络回调返回MOC所在队列,只要在回调中将所有Core Data操作提交给对应MOC的perform/performAndWait方法即可,这是苹果官方推荐的跨线程操作Core Data的标准方案。
现有代码的可优化点
- 你当前在
performBackgroundTask闭包中发起异步网络请求后直接调用moc2.save(),此时网络请求尚未返回,所有修改操作都还没执行,保存逻辑无效。 updatePrices中的while循环+Thread.sleep会永久阻塞当前所在线程,性能极低,建议替换为GCD重复定时器。- 不需要为每个网络请求单独创建新的队列,
getYahooQuote2传入全局并发队列即可,不需要和MOC队列绑定。
具体改造步骤
步骤1:优化定时更新逻辑
替换原有的while循环为GCD定时器,避免线程阻塞:
func startUpdatePriceTimer() { // 首次立即执行 updatePrices() // 启动5分钟重复定时器 let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global()) timer.schedule(deadline: .now() + 300, repeating: 300) timer.setEventHandler { [weak self] in guard let self = self, Functions().isMarketOpen(date: Date()) else { return } self.updatePrices() } timer.resume() }
步骤2:改造价格更新逻辑,用DispatchGroup跟踪异步请求
func updatePrices() { guard let app = NSApplication.shared.delegate as? AppDelegate, let mvc = app.myViewController else { return } app.persistentContainer.performBackgroundTask { moc2 in do { // 取出需要更新的标的 guard let investments = try moc2.fetch(self.getInvestFetch()) as? [InvestmentMO] else { return } let needUpdateInvestments = investments.filter { $0.needsPriceUpdate } // 主线程更新UI DispatchQueue.main.async { mvc.setProgressIndicator(maxVal: Double(needUpdateInvestments.count - 1)) } // 用DispatchGroup跟踪所有请求 let group = DispatchGroup() for investment in needUpdateInvestments { // 提前存对象ID,避免跨线程访问托管对象 let investId = investment.objectID let symbol = investment.symbol ?? "" group.enter() // 发起网络请求 Functions().getYahooQuote2(symbol: symbol, queue: .global()) { quoteParent in // 所有Core Data操作提交给moc2的专属队列 moc2.perform { defer { group.leave() } guard let result = quoteParent.quoteResponse.result, result.count > 0, let priceDec = result[0].regularMarketPrice, // 通过ID获取当前上下文下的托管对象,避免线程问题 let investment = moc2.object(with: investId) as? InvestmentMO else { return } // 这里写你原来的所有更新属性的逻辑 investment.price = NSDecimalNumber(decimal: priceDec) if let priceTime = result[0].regularMarketTime { investment.priceDate = NSDate(timeIntervalSince1970: Double(priceTime)) } else { investment.priceDate = NSDate() } // 剩下的holding、trade更新逻辑和原来一致 for holding in investment.holdingArray.filter({$0.isOpen()}) { holding.holdingPrice = investment.price holding.holdingValue = holding.quantity.multiplying(by: holding.holdingPrice) holding.gain = holding.holdingValue.subtracting(holding.cost) if holding.gain.decimalValue != 0 && holding.cost.decimalValue > 0 { holding.gainPerc = holding.gain.dividing(by: holding.cost) } else { holding.gainPerc = 0 } if let account = holding.account { let sum = account.holdingArray.map({($0.holdingValue.decimalValue)}).reduce(0, +) let costtot = account.holdingArray.map({($0.cost.decimalValue)}).reduce(0, +) account.accountValue = NSDecimalNumber(decimal: sum) account.cost = NSDecimalNumber(decimal: costtot) account.gain = account.accountValue.subtracting(account.cost ?? 0) if account.gain?.decimalValue != 0 && account.cost?.decimalValue ?? 0 > 0 { account.gainPerc = account.gain?.dividing(by: account.cost!) } else { account.gainPerc = 0 } } } for trade in investment.tradeArray.filter({!$0.executed}) { trade.tradePrice = investment.price trade.tradeValue = investment.price.multiplying(by: trade.quantity ?? 0) trade.gc = trade.getGC() trade.tradeCost = trade.tradeValue?.adding(trade.gc ?? 0) trade.nc = trade.getNC() } // 更新进度 DispatchQueue.main.async { mvc.reportProgress(msg: "Updating investment prices") } } } } // 等待所有请求处理完成再保存 group.wait() if moc2.hasChanges { DispatchQueue.main.async { mvc.setReadyString(rdst: "Saving Changes") } try moc2.save() } // 更新UI为就绪状态 DispatchQueue.main.async { mvc.setProgressIndicator(maxVal: 0) mvc.setReadyString(rdst: "Ready") } } catch { print(error) } } }
步骤3:修正获取请求的返回类型
你的getInvestFetch可以改成返回明确类型,减少类型转换的麻烦:
func getInvestFetch() -> NSFetchRequest<InvestmentMO> { let investFetch = NSFetchRequest<InvestmentMO>(entityName: "Investment") let fetchDate = Date().addMinutes(min: -5).nsDate let pred1 = NSPredicate(format: "(investmentType == 1 OR investmentType == 2) AND holdings.@count > 0") let pred2 = NSPredicate(format: "priceDate <= %@", fetchDate) investFetch.predicate = NSCompoundPredicate(type: .and, subpredicates: [pred1, pred2]) investFetch.returnsObjectsAsFaults = false return investFetch }
关键说明
- 所有对
moc2以及从moc2取出的托管对象的操作,必须包裹在moc2.perform/moc2.performAndWait中,不管你当前处于哪个线程,这样就完全规避了线程不匹配的问题。 - 通过保存托管对象的
objectID跨队列传递,再在目标上下文重新获取对象,是最安全的跨线程操作托管对象的方式,避免直接访问其他队列的托管对象导致崩溃。 - 用
DispatchGroup可以准确等待所有异步网络请求处理完成后再执行保存逻辑,避免漏存数据。
内容的提问来源于stack exchange,提问作者kanaloa
相关产品推荐
相关产品推荐

