如何等待异步函数loadUrl执行完成后再执行后续代码?
解决Swift异步操作时序问题:确保CoreData等待网络请求完成
咱们先把问题根源说透:loadUrl是异步网络请求,它的回调闭包只会在网络请求完成后才触发,但你当前的代码里,CoreData的savePhotoObject是在主流程同步执行的——这就导致保存操作先于回调里的cityState赋值完成,自然会拿到空值。从你的控制台输出也能清晰看到:After If和CoreData相关的执行逻辑先打印,之后才是回调里的内容,完全符合异步操作的特性。
下面给你两种实用的解决方案:
方案一:将CoreData保存逻辑移到异步回调内部
这是最直接的修复方式,不需要改动loadUrl的现有实现,只需要把依赖cityState的代码放到回调里,确保只有当cityState被正确赋值后才执行保存:
if locationSwitch.isOn == true { let url = "https://maps.googleapis.com/maps/api/geocode/json?latlng=\(location.latitude),\(location.longitude)&result_type=locality&key=AIzaSyDI-ZacHyPbLchRhkoaUTDokwj--z_a_jk" loadUrl(url: url) { optionalLocation in guard let nonOptionalLocation = optionalLocation else { // 处理location为空的错误情况,比如给cityState设默认值后再保存 cityState = "" CoreDataHandler.savePhotoObject(locationCoordinateLatitude: lat, locationCoordinateLongitude: lng, locationLocality: cityState, dateCreated: date, discription: discriptionBox.text!, photo: selectedImageData!) return } // 赋值并执行保存 cityState = nonOptionalLocation print("cityState: \(cityState)") print("Within Func") // 把CoreData保存移到这里! CoreDataHandler.savePhotoObject(locationCoordinateLatitude: lat, locationCoordinateLongitude: lng, locationLocality: cityState, dateCreated: date, discription: discriptionBox.text!, photo: selectedImageData!) } print("Within If") } else { lat = 0.0 lng = 0.0 cityState = "" // else分支也要执行保存 CoreDataHandler.savePhotoObject(locationCoordinateLatitude: lat, locationCoordinateLongitude: lng, locationLocality: cityState, dateCreated: date, discription: discriptionBox.text!, photo: selectedImageData!) } print("After If")
这样修改后,无论locationSwitch是否开启,CoreData的保存操作都会在cityState确定后才执行,彻底解决时序问题。
方案二:用Async/Await重构异步逻辑(Swift 5.5+)
如果你使用的是Swift 5.5及以上版本(对应iOS 15+/macOS 12+),可以把loadUrl改成异步函数,用async/await让代码以同步的写法处理异步操作,可读性和维护性都会更好:
第一步:重构loadUrl函数
把原来的闭包回调改成异步返回:
// 先定义解析JSON需要的模型(根据谷歌地理编码API的返回结构调整) struct GeocodeResponse: Codable { let results: [GeocodeResult] } struct GeocodeResult: Codable { let formattedAddress: String // 匹配API返回的键名 enum CodingKeys: String, CodingKey { case formattedAddress = "formatted_address" } } func loadUrl(url: String) async -> String? { guard let url = URL(string: url) else { return nil } do { let (data, _) = try await URLSession.shared.data(from: url) let result = try JSONDecoder().decode(GeocodeResponse.self, from: data) return result.results.first?.formattedAddress } catch { print("请求或解析错误:\(error)") return nil } }
第二步:调用异步函数并等待结果
在调用处用await等待loadUrl完成,之后再执行CoreData保存:
Task { // 在异步任务中执行await操作 if locationSwitch.isOn == true { let url = "https://maps.googleapis.com/maps/api/geocode/json?latlng=\(location.latitude),\(location.longitude)&result_type=locality&key=AIzaSyDI-ZacHyPbLchRhkoaUTDokwj--z_a_jk" let optionalLocation = await loadUrl(url: url) guard let nonOptionalLocation = optionalLocation else { cityState = "" CoreDataHandler.savePhotoObject(locationCoordinateLatitude: lat, locationCoordinateLongitude: lng, locationLocality: cityState, dateCreated: date, discription: discriptionBox.text!, photo: selectedImageData!) return } cityState = nonOptionalLocation print("cityState: \(cityState)") } else { lat = 0.0 lng = 0.0 cityState = "" } // 统一执行保存操作 CoreDataHandler.savePhotoObject(locationCoordinateLatitude: lat, locationCoordinateLongitude: lng, locationLocality: cityState, dateCreated: date, discription: discriptionBox.text!, photo: selectedImageData!) print("After If") }
这种方式避免了嵌套回调的混乱,代码逻辑更线性,也更容易排查问题。
总结一下:方案一适合快速修复现有代码,方案二更适合长期维护的现代Swift项目,你可以根据自己的项目版本和需求选择。
内容的提问来源于stack exchange,提问作者Patrick Haertel
相关产品推荐
相关产品推荐

