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

异步代码的Task {}应在ViewModel还是ViewController中使用?

Swift异步代码:Task包装位置与async函数的最佳实践

结论先行

将ViewModel的函数标记为async,然后在ViewController的Task代码块中调用,这是完全可行且符合Swift Concurrency设计理念的标准方案,比在ViewModel里包装Task的方式更简洁、易维护。

两种方案的优劣对比

1. ViewModel内部用Task {…}包装异步逻辑

这种做法会让ViewModel的对外接口变成同步函数,但存在明显弊端:

  • 浪费了Swift Concurrency的结构化并发优势,没法直接用async/await的简洁语法
  • 必须依赖回调、代理、RxSwift等传统方式传递异步结果,代码冗余且容易陷入回调地狱
  • 调用方无法从函数签名直观判断这是异步操作,增加了理解和维护成本

2. ViewModel函数标记为async,在ViewController的Task中调用

这是当前Swift异步编程的推荐模式,优势非常明显:

  • 函数签名清晰,async关键字直接表明这是一个需要等待的异步操作
  • 无需额外的回调或代理,通过await就能获取结果,代码结构更扁平
  • 可以利用结构化并发的特性,比如任务取消、错误处理,同时能方便地切回主队列更新UI

代码示例

ViewModel实现

class DataViewModel {
    // 明确标记为async函数,暴露异步逻辑
    func fetchUserData() async throws -> [User] {
        let url = URL(string: "https://api.example.com/users")!
        let (data, response) = try await URLSession.shared.data(from: url)
        
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            throw NetworkError.invalidResponse
        }
        
        return try JSONDecoder().decode([User].self, from: data)
    }
}

enum NetworkError: Error {
    case invalidResponse
}

struct User: Codable {
    let id: Int
    let name: String
}

ViewController调用

class UserListViewController: UIViewController {
    private let viewModel = DataViewModel()
    private var users: [User] = []
    private let tableView = UITableView()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
        fetchUsers()
    }

    private func fetchUsers() {
        // 在Task中调用ViewModel的async函数
        Task {
            do {
                users = try await viewModel.fetchUserData()
                // 切回主队列更新UI
                await MainActor.run {
                    tableView.reloadData()
                }
            } catch {
                await MainActor.run {
                    showError(message: error.localizedDescription)
                }
            }
        }
    }

    // 其他UI相关方法...
    private func setupTableView() {
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UserCell")
        tableView.dataSource = self
        tableView.frame = view.bounds
        view.addSubview(tableView)
    }

    private func showError(message: String) {
        let alert = UIAlertController(title: "出错了", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "确定", style: .default))
        present(alert, animated: true)
    }
}

// UITableViewDataSource实现...
extension UserListViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        users.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath)
        cell.textLabel?.text = users[indexPath.row].name
        return cell
    }
}

额外提示

  • 如果你的ViewController被标记为@MainActor,那么Task中的代码默认运行在主队列,更新UI时可以省去MainActor.run的调用
  • 记得处理任务取消:比如在ViewController的viewWillDisappear中取消对应的Task,避免内存泄漏或无效的网络请求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 15:15:28