SwiftUI视图中如何优雅等待Task完成以正确保存数据?
解决方案:替代延迟等待,可靠触发任务完成后的保存操作
核心问题分析
你当前使用DispatchQueue.main.asyncAfter的方式完全依赖固定延迟,无法适配网络请求、地理编码的实际耗时,要么会保存旧值,要么会做不必要的等待。我们可以通过异步流程管控+状态监听的方式,确保所有任务完成后再执行保存逻辑,同时不阻塞UI。
步骤1:重构Networking类,用async/await管控异步流程
先把闭包式的异步API改为async/await,让任务执行顺序更清晰,同时添加状态标识来通知视图任务完成情况:
import CoreLocation class Networking: NSObject, ObservableObject, CLLocationManagerDelegate { @Published public var lastLocation: CLLocation? @Published public var locationString: CLPlacemark? @Published private(set) var isProcessingLocation = false // 标记任务是否在执行 private var locationManager = CLLocationManager() // 重构地理编码方法为async/await形式 private func getLocationString() async throws -> CLPlacemark? { guard let location = lastLocation else { return nil } let geocoder = CLGeocoder() let placemarks = try await geocoder.reverseGeocodeLocation(location) return placemarks.first } // 重构天气、AQI请求为async/await(假设原方法是闭包形式,这里改为异步) private func getMainWeather(_ lat: Double, _ lon: Double) async { // 替换为你的异步请求实现,比如URLSession.data(from:) } private func getAQI(_ lat: Double, _ lon: Double) async { // 替换为你的异步请求实现 } // 用自定义方法触发位置处理,替代直接设置lastLocation的didSet public func processLocation(_ location: CLLocation) { lastLocation = location isProcessingLocation = true Task { do { // 等待地理编码完成,再更新locationString if let placemark = try await getLocationString() { await MainActor.run { self.locationString = placemark } } // 依次执行其他异步任务 await getMainWeather(location.coordinate.latitude, location.coordinate.longitude) await getAQI(location.coordinate.latitude, location.coordinate.longitude) } catch { print("位置处理出错:\(error.localizedDescription)") } // 任务全部完成,更新状态并停止定位 await MainActor.run { self.isProcessingLocation = false locationManager.stopUpdatingLocation() } } } }
步骤2:重构视图与SavedLocations,监听状态触发保存
把SavedLocations改为ObservableObject统一管理本地存储,同时用SwiftUI的onChange监听Networking的任务状态,确保只有任务完成后才执行保存:
struct LocationSearch: View { @StateObject var locationService = LocationService() @ObservedObject var networking: Networking @StateObject private var savedLocations = SavedLocations() // 单实例管理存储 @Environment(\.dismiss) private var dismiss var body: some View { ForEach(locationService.searchResults, id: \.self) { location in Button(location.title) { getCoordinate(addressString: location.title) { coordinates, error in guard error == nil, let coordinates = coordinates else { return } let newLocation = CLLocation(latitude: coordinates.latitude, longitude: coordinates.longitude) // 调用自定义方法触发位置处理 networking.processLocation(newLocation) } } } // 监听任务完成状态,触发保存与页面关闭 .onChange(of: networking.isProcessingLocation) { isProcessing in guard !isProcessing, let locality = networking.locationString?.locality else { return } // 可选:避免重复添加相同地点 if !savedLocations.all.contains(locality) { savedLocations.all.append(locality) savedLocations.save() dismiss() } } } // 保留原有的坐标获取方法(若可能也建议改为async/await) private func getCoordinate(addressString: String, completion: @escaping (CLLocationCoordinate2D?, Error?) -> Void) { // 原实现逻辑 } } // 统一管理本地存储的类 class SavedLocations: ObservableObject { @Published var all: [String] = [] init() { // 初始化时读取UserDefaults中的旧数据 if let saved = UserDefaults.standard.array(forKey: "savedLocations") as? [String] { all = saved } } // 封装保存逻辑 func save() { UserDefaults.standard.set(all, forKey: "savedLocations") } }
关键改进点说明
- async/await管控异步流程:确保地理编码、天气、AQI请求按顺序完成,避免任务执行混乱。
- 状态标识监听:通过
isProcessingLocation让视图精准感知任务完成时机,替代不可靠的固定延迟。 - 统一存储管理:
SavedLocations作为ObservableObject,避免每次点击按钮新建实例导致的旧数据丢失问题。 - 主线程更新UI:用
MainActor.run确保@Published属性的更新在主线程执行,符合SwiftUI的线程安全要求。
内容的提问来源于stack exchange,提问作者John Harrington
相关产品推荐
相关产品推荐

