Swift并发问题:是否应让服务协议遵循Sendable?
解决严格并发检查下的Sendable警告问题
我通过协议实现多个服务以便在ViewModel初始化时注入Mock服务,开启严格并发检查后出现大量警告:"Capture of 'self' with non-sendable type "ViewModelType" in 'async let' binding"
最小复现示例
class ViewController: UIViewController { let viewModel: UserProfileViewModel init(service: UserProfileServiceProtocol) { self.viewModel = UserProfileViewModel(service: service) super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() getUserProfileData() // Do any additional setup after loading the view. } func getUserProfileData() { Task { do { try await viewModel.getProfileData() // update UI } catch { print(error) } } } } struct UserModel { var userID: String var username: String var profilePictureURL: URL? var profilePhoto: UIImage? } class UserProfileViewModel { private let service: any UserProfileServiceProtocol var user: UserModel? var postsCount: Int? var followersCount: Int? var followedUsersCount: Int? init(service: UserProfileServiceProtocol) { self.service = service } func getProfileData() async throws { async let user = service.getUserData() async let followersCount = service.getFollowersCount() async let followedUsersCount = service.getFollowingCount() async let postsCount = service.getPostsCount() self.user = try await user self.followersCount = try await followersCount self.followedUsersCount = try await followedUsersCount self.postsCount = try await postsCount } } protocol UserProfileServiceProtocol { var followService: FollowSystemProtocol { get } var userPostsService: UserPostsServiceProtocol { get } var userDataService: UserDataServiceProtocol { get } func getFollowersCount() async throws -> Int func getFollowingCount() async throws -> Int func getPostsCount() async throws -> Int func getUserData() async throws -> UserModel } protocol FollowSystemProtocol { func getFollowersNumber(for uid: String) async throws -> Int func getFollowingNumber(for uid: String) async throws -> Int } protocol UserPostsServiceProtocol { func getPostCount(for userID: String) async throws -> Int } protocol UserDataServiceProtocol { func getUser(for userID: String) async throws -> UserModel } class UserService: UserProfileServiceProtocol { let userID: String let followService: FollowSystemProtocol let userPostsService: UserPostsServiceProtocol let userDataService: UserDataServiceProtocol init(userID: String, followService: FollowSystemProtocol, userPostsService: UserPostsServiceProtocol, userDataService: UserDataServiceProtocol) { self.userID = userID self.followService = followService self.userPostsService = userPostsService self.userDataService = userDataService } func getFollowersCount() async throws -> Int { let followersCount = try await followService.getFollowersNumber(for: userID) return followersCount } func getFollowingCount() async throws -> Int { let followersCount = try await followService.getFollowingNumber(for: userID) return followersCount } func getPostsCount() async throws -> Int { let postsCount = try await userPostsService.getPostCount(for: userID) return postsCount } func getUserData() async throws -> UserModel { let user = try await userDataService.getUser(for: userID) return user } } class FollowSystemService: FollowSystemProtocol { func getFollowersNumber(for uid: String) async throws -> Int { try await Task.sleep(for: .seconds(1)) return 5 } func getFollowingNumber(for uid: String) async throws -> Int { try await Task.sleep(for: .seconds(1)) return 19 } } class UserPostsService: UserPostsServiceProtocol { func getPostCount(for userID: String) async throws -> Int { try await Task.sleep(for: .seconds(1)) return 27 } } class UserProfileService: UserDataServiceProtocol { func getUser(for userID: String) async throws -> UserModel { try await Task.sleep(for: .seconds(1)) return UserModel(userID: "testUser_01", username: "testUser", profilePictureURL: nil) } }
解决方案
1. 让服务协议遵循Sendable(推荐,常见做法)
严格并发检查要求被异步任务捕获的类型必须符合Sendable协议,确保跨线程访问安全。服务类通常是无状态或内部已处理线程安全的,让协议遵循Sendable是合理且常见的做法:
protocol UserProfileServiceProtocol: Sendable { var followService: any FollowSystemProtocol { get } var userPostsService: any UserPostsServiceProtocol { get } var userDataService: any UserDataServiceProtocol { get } func getFollowersCount() async throws -> Int func getFollowingCount() async throws -> Int func getPostsCount() async throws -> Int func getUserData() async throws -> UserModel } protocol FollowSystemProtocol: Sendable { func getFollowersNumber(for uid: String) async throws -> Int func getFollowingNumber(for uid: String) async throws -> Int } protocol UserPostsServiceProtocol: Sendable { func getPostCount(for userID: String) async throws -> Int } protocol UserDataServiceProtocol: Sendable { func getUser(for userID: String) async throws -> UserModel }
2. 标记ViewModel为@MainActor
ViewModel的属性最终要更新UI,将ViewModel标记为@MainActor可以确保所有属性的读写都在主线程执行,避免数据竞争,同时也能解决部分Sendable相关警告:
@MainActor class UserProfileViewModel { // 原有代码保持不变 }
3. 临时抑制警告(不推荐)
如果确定当前代码不会有数据竞争,可以临时用编译指令抑制警告,但这会绕过Swift的并发安全检查,只适合临时调试:
func getProfileData() async throws { // swiftlint:disable:next sendable async let user = service.getUserData() // 其他async let同理添加抑制 // ... }
或者让ViewModel遵循@unchecked Sendable,但同样要确保自己处理线程安全:
class UserProfileViewModel: @unchecked Sendable { // 原有代码 }
内容的提问来源于stack exchange,提问作者adseeAnn
相关产品推荐
相关产品推荐

