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

Swift问题:无法在闭包外部使用闭包内初始化的Pokemon对象

解决Swift异步请求后闭包外部数据为nil的问题

这是异步代码执行顺序导致的问题:getData是异步方法,网络请求需要时间才能完成,而闭包外部的代码会立即执行,此时请求还没结束,pokeList自然还没被赋值,所以是nil。下面是具体的解决方法:

方案1:在闭包内处理数据/更新UI

所有依赖pokeList的操作都要放在请求完成的闭包内部,或者由闭包调用专门的处理方法,确保数据准备好后再执行:

class ViewController: UIViewController {
    var pokeList: [PokemonEntry]?
    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        
        Webservice.shared.getData { [weak self] result in
            guard let self = self else { return }
            switch result {
            case .success(let pokemonEntries):
                self.pokeList = pokemonEntries
                // 数据就绪后更新UI或处理逻辑
                self.updatePokemonUI()
            case .failure(let error):
                print("请求出错:\(error.localizedDescription)")
            }
        }
        // 这里的代码会先于闭包执行,pokeList此时为nil,不要在这里使用它
    }

    private func updatePokemonUI() {
        // UI操作必须在主线程执行
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }
}

// 示例TableView数据源实现
extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return pokeList?.count ?? 0
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "PokemonCell", for: indexPath)
        cell.textLabel?.text = pokeList?[indexPath.row].name
        return cell
    }
}

方案2:使用Swift 5.5+的Async/Await(推荐)

Async/Await让异步代码更简洁,避免回调嵌套,改造你的Webservice方法为异步函数:

class Webservice {
    static let shared = Webservice()
    
    // 改为异步抛出函数
    func getData() async throws -> [PokemonEntry] {
        guard let url = URL(string: "https://pokeapi.co/api/v2/pokemon?limit=20") else {
            throw NSError(domain: "InvalidURL", code: 0, userInfo: [NSLocalizedDescriptionKey: "无效的API地址"])
        }
        
        let (data, response) = try await URLSession.shared.data(from: url)
        
        // 验证响应状态码
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            throw NSError(domain: "RequestFailed", code: 0, userInfo: [NSLocalizedDescriptionKey: "请求失败"])
        }
        
        // 解析数据到你的模型
        let pokemonResponse = try JSONDecoder().decode(Pokemon.self, from: data)
        return pokemonResponse.results // 假设Pokemon模型包含results字段,类型为[PokemonEntry]
    }
}

然后在ViewController中用Task调用异步方法:

class ViewController: UIViewController {
    var pokeList: [PokemonEntry]?
    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        
        // 用Task启动异步任务
        Task {
            do {
                self.pokeList = try await Webservice.shared.getData()
                // 更新UI切回主线程
                await MainActor.run {
                    self.tableView.reloadData()
                }
            } catch {
                print("请求失败:\(error.localizedDescription)")
            }
        }
    }
}

// TableView数据源实现同方案1
extension ViewController: UITableViewDataSource {
    // ... 同上
}

关键注意点

  • 异步执行顺序:网络请求是耗时操作,主线程不会等待它完成,所以闭包外/Task外的代码会先执行,此时pokeList还未赋值。
  • 避免内存泄漏:闭包中使用[weak self],防止ViewController被循环引用无法释放。
  • UI操作必须在主线程:不管用哪种方案,更新UI的代码都要确保在主线程执行(Async/Await中可以用MainActor.run,闭包中用DispatchQueue.main.async)。

内容的提问来源于stack exchange,提问作者retrodevmetro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 08:45:39