如何保留@escaping闭包的返回结果,避免数据丢失?
问题:闭包外变量无法保存异步请求返回的数据
我需要实现一个接收邮政编码(postalCode)并返回城市、州和国家的函数。目前函数能成功获取结果,在调用的@escaping闭包内可以正常打印数据,但把数据保存到闭包外的变量后,数据就丢失了。
我的代码
func getAddress(forPostalCode postalCode: String, completion: @escaping (_ city: String?, _ state: String?, _ country: String?, _ error: Error?) -> Void) { let geocoder = CLGeocoder() let addressString = "\(postalCode), USA" geocoder.geocodeAddressString(addressString) { placemarks, error in if let error = error { completion(nil, nil, nil, error) return } guard let placemark = placemarks?.first else { completion(nil, nil, nil, NSError(domain: "com.example.app", code: 1, userInfo: [NSLocalizedDescriptionKey: "No placemarks found"])) return } guard let city = placemark.locality else { completion(nil, nil, nil, NSError(domain: "com.example.app", code: 2, userInfo: [NSLocalizedDescriptionKey: "City not found"])) return } guard let state = placemark.administrativeArea else { completion(nil, nil, nil, NSError(domain: "com.example.app", code: 3, userInfo: [NSLocalizedDescriptionKey: "State not found"])) return } guard let country = placemark.country else { completion(nil, nil, nil, NSError(domain: "com.example.app", code: 4, userInfo: [NSLocalizedDescriptionKey: "Country not found"])) return } completion(city, state, country, nil) } } let postalCode = "10001" var aCity: String = "" var aState: String = "" var aCountry: String = "" getAddress(forPostalCode: postalCode) { city, state, country, error in if let error = error { print("Error: \(error.localizedDescription)") return } if let city = city, let state = state, let country = country { aCity = city aState = state aCountry = country print("Internal: \(aCity), \(aState) in \(aCountry)") } else { print("Error: Unable to retrieve address for postal code \(postalCode)") } } print("External: \(aCity), \(aState) in \(aCountry)")
运行结果
External: , in Internal: New York, NY in United States
解答
这是因为geocodeAddressString是异步操作,它会在后台线程执行地理编码请求,不会阻塞主线程。代码的实际执行顺序是:
- 初始化
aCity、aState、aCountry为空字符串 - 调用
getAddress,发起异步地理编码请求 - 直接执行闭包外的
print("External: ..."),此时异步请求还未完成,变量仍为初始空值 - 异步请求完成后,才会进入闭包内部给变量赋值并打印
Internal: ...
解决方案
所有依赖异步请求结果的操作,必须放在闭包内部执行,或者使用Swift现代的async/await语法处理异步逻辑:
方案1:在闭包内处理结果(最直接)
把需要使用地址数据的逻辑全部移到闭包内部:
getAddress(forPostalCode: postalCode) { city, state, country, error in if let error = error { print("Error: \(error.localizedDescription)") return } guard let city = city, let state = state, let country = country else { print("Error: Unable to retrieve address for postal code \(postalCode)") return } // 在这里执行所有需要用到地址数据的操作 print("Address: \(city), \(state) in \(country)") // 比如更新UI、保存到本地等 // updateUserInterface(city: city, state: state, country: country) }
方案2:改用async/await(更简洁的异步写法)
将原函数改造为支持async/await的版本,避免闭包嵌套:
import CoreLocation func getAddress(forPostalCode postalCode: String) async throws -> (city: String, state: String, country: String) { let geocoder = CLGeocoder() let addressString = "\(postalCode), USA" let placemarks = try await geocoder.geocodeAddressString(addressString) guard let placemark = placemarks.first else { throw NSError(domain: "com.example.app", code: 1, userInfo: [NSLocalizedDescriptionKey: "No placemarks found"]) } guard let city = placemark.locality else { throw NSError(domain: "com.example.app", code: 2, userInfo: [NSLocalizedDescriptionKey: "City not found"]) } guard let state = placemark.administrativeArea else { throw NSError(domain: "com.example.app", code: 3, userInfo: [NSLocalizedDescriptionKey: "State not found"]) } guard let country = placemark.country else { throw NSError(domain: "com.example.app", code: 4, userInfo: [NSLocalizedDescriptionKey: "Country not found"]) } return (city, state, country) } // 调用示例(需在异步上下文,比如Task中) let postalCode = "10001" Task { do { let address = try await getAddress(forPostalCode: postalCode) print("Address: \(address.city), \(address.state) in \(address.country)") // 在这里处理地址数据 } catch { print("Error: \(error.localizedDescription)") } }
内容的提问来源于stack exchange,提问作者indyMac
相关产品推荐
相关产品推荐

