Swift闭包函数执行延迟问题:调用getData后getPr返回空数组
解决异步闭包延迟导致返回空数组的问题
这事儿我太熟了!问题的核心是网络请求是异步操作——你调用pc.getData()后,代码会立刻往下执行print(pc.getPr()),但此时json.getRequest的闭包还在后台等网络请求返回,根本没来得及给products赋值,所以自然返回空数组啦。
下面给你几个实用的解决方案,按需选就行:
方案1:给getData加Completion Handler(最基础的回调写法)
修改ProductController的getData方法,让它接收一个闭包参数,等网络请求完成后再通知外部拿数据:
class ProductController: NSObject { var products = [Product]() // 新增completion回调参数 func getData(completion: @escaping () -> Void){ let json = JSONParser() let path = "http://localhost:8888/myApi/product/read.php" json.getRequest(url: path) { (products) in self.products = products print("closure") print(self.products) // 请求搞定后,调用回调告诉外部可以拿数据了 completion() } } func getPr() -> [Product]{ return self.products } }
调用的时候,把需要拿数据的逻辑放到completion闭包里:
let pc = ProductController() pc.getData { print(pc.getPr()) // 这里就能拿到正确的产品数组了 }
方案2:用Swift 5.5+的Async/Await(更简洁的异步语法)
如果你的项目已经升级到Swift 5.5及以上,强烈推荐用Async/Await替代闭包,代码清爽太多:
首先得把JSONParser的getRequest封装成异步方法(适配原来的闭包版):
extension JSONParser { func getRequestAsync(url: String) async throws -> [Product] { return try await withCheckedThrowingContinuation { continuation in self.getRequest(url: url) { products in continuation.resume(returning: products) } } } }
然后修改ProductController的方法:
class ProductController: NSObject { var products = [Product]() func getData() async { let json = JSONParser() let path = "http://localhost:8888/myApi/product/read.php" do { self.products = try await json.getRequestAsync(url: path) print("async await 执行完成") print(self.products) } catch { print("请求失败:\(error)") } } func getPr() -> [Product]{ return self.products } }
调用的时候,要在异步上下文里执行(比如用Task包裹):
Task { let pc = ProductController() await pc.getData() print(pc.getPr()) // 这里能拿到正确数据 }
方案3:用Combine框架(响应式编程玩法)
如果你的项目已经在用Combine,那可以用CurrentValueSubject来发布数据更新,外部订阅就能实时拿到变化:
import Combine class ProductController: NSObject { // 用CurrentValueSubject保存产品数组,初始值为空,同时可以发布更新 private let productsSubject = CurrentValueSubject<[Product], Never>([]) // 对外暴露只读的Publisher,防止外部随便修改 var productsPublisher: AnyPublisher<[Product], Never> { productsSubject.eraseToAnyPublisher() } func getData(){ let json = JSONParser() let path = "http://localhost:8888/myApi/product/read.php" json.getRequest(url: path) { (products) in self.productsSubject.send(products) // 发送新的产品数组 print("closure") print(products) } } }
调用的时候,订阅productsPublisher就能拿到数据:
let pc = ProductController() // 订阅数据更新,每次有新数据都会触发这个闭包 let cancellable = pc.productsPublisher.sink { products in print(products) } pc.getData() // 注意:cancellable要记得持有(比如存在属性里),不然会立刻取消订阅
总结一下:
- Completion Handler是最兼容旧版本的写法,适合所有Swift版本;
- Async/Await是现在Swift官方推荐的异步方式,代码更简洁;
- Combine适合响应式场景的项目,能方便处理数据变化。
根据你的项目情况选就行~
内容的提问来源于stack exchange,提问作者Dan Sali
相关产品推荐
相关产品推荐

