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

Swift 6中如何解决'Sending self.viewModel'的数据竞争风险?

解决Swift 6中调用viewModel.loadUsers()的数据竞争警告

核心问题分析

警告的根源在于:

  • ObservableObject的@Published属性必须在MainActor线程更新,但原代码中UserListViewModel未明确绑定MainActor
  • UserListViewModelProtocol协议无MainActor约束,导致Swift无法推断viewModel的线程安全特性
  • loadUsers()方法中直接修改@Published属性,未明确线程上下文

分步解决方案

1. 给ViewModel协议添加MainActor约束

修改协议,让所有遵循该协议的类型自动绑定到MainActor:

@MainActor
protocol UserListViewModelProtocol: ObservableObject {
    var users: [UserModel] { get }
    var isLoading: Bool { get }
    var error: String? { get }
    func loadUsers() async
}

2. 确保ViewModel继承MainActor约束

由于协议已标注@MainActor,UserListViewModel会自动继承该约束,无需额外添加(若需显式声明也可在类前加@MainActor)。

3. 修正loadUsers()的线程安全逻辑

userService.fetchUsers()是后台异步操作,await后会自动回到MainActor上下文,确保状态更新线程安全:

func loadUsers() async {
    isLoading = true
    error = nil
    do {
        // 后台执行网络请求,await后回到MainActor更新状态
        users = try await userService.fetchUsers()
    } catch {
        self.error = error.localizedDescription
    }
    isLoading = false
}

4. 视图中task调用无需修改

此时viewModel已明确为MainActor绑定类型,Swift 6的并发检查会认可传递self.viewModel的安全性,警告自动消失。


完整修正后的代码

import SwiftUI

// MARK: - Model
struct UserModel: Identifiable, Decodable {
    let id: Int
    let name: String
}

// MARK: - Protocol for User Service
protocol UserServiceProtocol {
    func fetchUsers() async throws -> [UserModel]
}

// MARK: - Real API Implementation
class UserService: UserServiceProtocol {
    func fetchUsers() async throws -> [UserModel] {
        let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode([UserModel].self, from: data)
    }
}

// MARK: - ViewModel Protocol
@MainActor
protocol UserListViewModelProtocol: ObservableObject {
    var users: [UserModel] { get }
    var isLoading: Bool { get }
    var error: String? { get }
    func loadUsers() async
}

// MARK: - ViewModel
class UserListViewModel: UserListViewModelProtocol {
    @Published private(set) var users: [UserModel] = []
    @Published private(set) var isLoading = false
    @Published private(set) var error: String? = nil

    private let userService: UserServiceProtocol

    init(userService: UserServiceProtocol = UserService()) {
        self.userService = userService
    }

    func loadUsers() async {
        isLoading = true
        error = nil
        do {
            users = try await userService.fetchUsers()
        } catch {
            self.error = error.localizedDescription
        }
        isLoading = false
    }
}

// MARK: - SwiftUI View
struct UserListView<VM: UserListViewModelProtocol>: View {
    @StateObject private var viewModel: VM

    init(viewModel: @autoclosure @escaping () -> VM) {
        _viewModel = StateObject(wrappedValue: viewModel())
    }

    var body: some View {
        NavigationView {
            Group {
                if viewModel.isLoading {
                    ProgressView("Loading...")
                } else if let error = viewModel.error {
                    Text("❌ \(error)")
                        .foregroundColor(.red)
                } else {
                    List(viewModel.users) { user in
                        Text(user.name)
                    }
                }
            }
            .navigationTitle("Users")
        }
        .task {
            await viewModel.loadUsers() // 警告已消除
        }
    }
}

// MARK: - Mock for Preview & Tests
class MockUserService: UserServiceProtocol {
    func fetchUsers() async throws -> [UserModel] {
        return [
            UserModel(id: 1, name: "Mock Alice"),
            UserModel(id: 2, name: "Mock Bob")
        ]
    }
}

// MARK: - Preview
struct UserListView_Previews: PreviewProvider {
    static var previews: some View {
        UserListView(viewModel: UserListViewModel(userService: MockUserService()))
    }
}

方案说明

  • @MainActor标注协议后,所有遵循协议的ViewModel都会被约束在主线程运行,确保@Published属性的更新始终安全
  • Swift 6并发检查能识别viewModel的线程安全特性,不再触发数据竞争警告
  • 未使用全局屏蔽或unsafe手段,完全符合Swift并发安全规范

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 14:55:06