Swift中如何让@Published变量正确更新?附地址转坐标问题
问题分析
你遇到的不是@Published变量没被赋值,而是异步操作的执行顺序问题:
getCoordinate里的geocodeAddressString是异步方法,它会在后台线程执行地理编码,完成后才会调用completion闭包。- 你在
saveMapData里调用convertAddress后立刻打印userAddressLat和userAddressLong,此时异步的地理编码任务还没完成,变量还是初始值0.0。而convertAddress闭包里的打印是在异步任务完成后执行的,所以能拿到正确值。
解决方案
有两种常见的修复方式,根据你的iOS版本选择:
方式1:给convertAddress添加Completion Handler
修改convertAddress方法,让它完成编码后通知调用者,这样你就能在正确的时机保存数据:
final class PublishMapData: ObservableObject { @Published var userAddressLat: Double = 0.0 @Published var userAddressLong: Double = 0.0 func saveMapData(address: String){ let address = "One Apple Park Way, Cupertino, CA 95014" convertAddress(address: address) { [weak self] in guard let self = self else { return } // 这里异步任务已经完成,可以拿到正确的坐标值 print(String(self.userAddressLat)) print(String(self.userAddressLong)) // 在这里执行存入Firestore的操作 } } // 添加completion参数 func convertAddress(address: String, completion: @escaping () -> Void) { getCoordinate(addressString: address) { (location, error) in if error != nil { completion() return } DispatchQueue.main.async { self.userAddressLat = location.latitude self.userAddressLong = location.longitude completion() // 编码完成后触发回调 } } } private func getCoordinate(addressString : String, completionHandler: @escaping(CLLocationCoordinate2D, NSError?) -> Void ) { let geocoder = CLGeocoder() geocoder.geocodeAddressString(addressString) { (placemarks, error) in if error == nil { if let placemark = placemarks?[0] { let location = placemark.location! completionHandler(location.coordinate, nil) return } } completionHandler(kCLLocationCoordinate2DInvalid, error as NSError?) } } }
方式2:使用Async/Await(iOS 15+、macOS 12+)
如果你的项目支持较新的系统版本,可以用Swift的并发框架简化异步代码,避免嵌套闭包:
import CoreLocation final class PublishMapData: ObservableObject { @Published var userAddressLat: Double = 0.0 @Published var userAddressLong: Double = 0.0 // 标记为async方法 func saveMapData(address: String) async { let address = "One Apple Park Way, Cupertino, CA 95014" do { let coordinate = try await convertAddress(address: address) userAddressLat = coordinate.latitude userAddressLong = coordinate.longitude // 这里可以直接执行存入Firestore的操作 print(String(userAddressLat)) print(String(userAddressLong)) } catch { print("地理编码失败:\(error)") } } // 转为async throws方法 func convertAddress(address: String) async throws -> CLLocationCoordinate2D { return try await withCheckedThrowingContinuation { continuation in getCoordinate(addressString: address) { (location, error) in if let error = error { continuation.resume(throwing: error) } else { continuation.resume(returning: location) } } } } private func getCoordinate(addressString : String, completionHandler: @escaping(CLLocationCoordinate2D, NSError?) -> Void ) { let geocoder = CLGeocoder() geocoder.geocodeAddressString(addressString) { (placemarks, error) in if error == nil { if let placemark = placemarks?[0] { let location = placemark.location! completionHandler(location.coordinate, nil) return } } completionHandler(kCLLocationCoordinate2DInvalid, error as NSError?) } } }
调用saveMapData时需要在async上下文里:
// 比如在View的task修饰符里 .task { let mapData = PublishMapData() await mapData.saveMapData(address: "测试地址") }
额外注意点
- 使用
[weak self]避免循环引用:在闭包里引用self时,建议加上[weak self]防止内存泄漏,尤其是在ObservableObject这类可能被View持有对象中。 - 错误处理:当前代码里忽略了错误情况,建议在编码失败时添加提示或处理逻辑,比如给用户显示错误弹窗。
内容的提问来源于stack exchange,提问作者Cslim
相关产品推荐
相关产品推荐

