Swift 6中如何解决'Sending self.viewModel'的数据竞争风险?
解决Swift 6中调用
viewModel.loadUsers()的数据竞争警告 核心问题分析
警告的根源在于:
ObservableObject的@Published属性必须在MainActor线程更新,但原代码中UserListViewModel未明确绑定MainActorUserListViewModelProtocol协议无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
相关产品推荐
相关产品推荐

