异步调用中使用@AppStorage存值触发紫色警告的解决方法
问题说明
在SwiftUI项目中,通过HTTP请求获取用户所在城市的人口率后,在异步回调内给标记了@AppStorage的lastSearchedCity和lastSearchedPopulationRate赋值时,触发了紫色线程警告:
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
相关代码片段:
if let pRate = populationRate{ self.lastSearchedCity = city // 紫色警告指向此行 self.lastSearchedPopulationRate = pRate // 紫色警告指向此行 }
试过用DispatchQueue.main.async包裹赋值,但编译器建议使用receive(on:)操作符,不清楚正确实现方式。
解决方案
方案1:用receive(on:)改造Combine请求管道(推荐)
如果你的HttpPopulationRateRequestor是基于Combine实现的HTTP请求,直接在请求的Combine管道中添加receive(on: DispatchQueue.main),确保后续的赋值逻辑切换到主线程执行:
// 假设请求方法返回AnyPublisher类型 func fetchPopulationRate(for city: String) -> AnyPublisher<Double, Error> { URLSession.shared.dataTaskPublisher(for: targetURL) .map(\.data) .decode(type: PopulationResponseModel.self, decoder: JSONDecoder()) .map(\.populationRate) .receive(on: DispatchQueue.main) // 关键:切换到主线程 .eraseToAnyPublisher() }
之后在订阅这个Publisher时直接赋值即可,不会触发警告:
fetchPopulationRate(for: currentCity) .sink(receiveCompletion: { _ in // 处理请求完成/错误逻辑 }, receiveValue: { pRate in self.lastSearchedCity = currentCity self.lastSearchedPopulationRate = pRate }) .store(in: &cancellables)
方案2:闭包场景下正确使用DispatchQueue.main.async
如果你的HTTP请求是基于闭包回调实现的,编译器的receive(on:)建议只是Combine场景下的最优解,闭包场景下用DispatchQueue.main.async完全合规,正确写法如下:
// 假设HTTP请求的闭包回调写法 HttpPopulationRateRequestor.fetchRate(for: city) { populationRate in if let pRate = populationRate { DispatchQueue.main.async { self.lastSearchedCity = city self.lastSearchedPopulationRate = pRate } } }
警告原因
@AppStorage是对UserDefaults的封装,同时遵循SwiftUI的状态发布机制,而SwiftUI要求所有视图状态的变更必须在主线程执行——因为UI更新操作只能在主线程完成,后台线程直接修改状态会触发线程安全警告。
内容的提问来源于stack exchange,提问作者fs_tigre

