You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 07:40:13