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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 19:43:13