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

Swift中如何让Alamofire响应返回至Core Data上下文所在同一后台线程

核心问题解决方法

你遇到的线程不匹配问题的本质是Core Data的托管对象上下文(MOC)只能在自身绑定的专属队列中操作,你不需要强制网络回调返回MOC所在队列,只要在回调中将所有Core Data操作提交给对应MOC的perform/performAndWait方法即可,这是苹果官方推荐的跨线程操作Core Data的标准方案。

现有代码的可优化点

  1. 你当前在performBackgroundTask闭包中发起异步网络请求后直接调用moc2.save(),此时网络请求尚未返回,所有修改操作都还没执行,保存逻辑无效。
  2. updatePrices中的while循环+Thread.sleep会永久阻塞当前所在线程,性能极低,建议替换为GCD重复定时器。
  3. 不需要为每个网络请求单独创建新的队列,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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.19 11:15:05