关于Set<AnyCancellable>的Sendable合规性及移除@unchecked Sendable替代方案的技术咨询
关于Set的Sendable合规性及移除@unchecked Sendable替代方案的技术咨询
嘿,这个问题我之前帮不少开发者捋清楚过,咱们一步步拆解解决:
首先得明白为什么编译器要求你加@unchecked Sendable——虽然AnyCancellable本身是符合Sendable协议的,但你声明的Set<AnyCancellable>是可变且非线程安全的。多个线程同时读写这个集合会引发数据竞争,编译器没法自动验证这个类的线程安全性,所以只能让你用@unchecked来手动确认。
现在你不想用@MainActor绑定主线程,也不想把整个类改成Actor,那最直接的解决方案就是给这个集合加一层线程安全的包装,用轻量级锁来同步所有读写操作,彻底消除数据竞争。
具体实现方案
自己写一个简单的线程安全集合类,用OSUnfairLock(比串行队列更高效的轻量级锁)来保护底层的Set<AnyCancellable>:
import Combine import os.lock final class ThreadSafeCancellableSet { private var underlyingSet = Set<AnyCancellable>() private let lock = OSUnfairLock() // 插入新的Cancellable func insert(_ cancellable: AnyCancellable) { lock.lock() defer { lock.unlock() } underlyingSet.insert(cancellable) } // 移除指定Cancellable func remove(_ cancellable: AnyCancellable) { lock.lock() defer { lock.unlock() } underlyingSet.remove(cancellable) } // 清空所有Cancellable func removeAll() { lock.lock() defer { lock.unlock() } underlyingSet.removeAll() } } // 扩展一下,让它兼容Combine的store(in:)语法 extension ThreadSafeCancellableSet { static func += (set: inout ThreadSafeCancellableSet, cancellable: AnyCancellable) { set.insert(cancellable) } }
然后修改你的TestSendable类,替换掉原来的Set:
final class TestSendable: Sendable { private var subscriptions = ThreadSafeCancellableSet() // 示例用法,完全不用跳主线程 func setupAsyncTask() { URLSession.shared.dataTaskPublisher(for: URL(string: "https://example.com")!) .sink( receiveCompletion: { completion in // 处理完成逻辑 }, receiveValue: { data in // 处理数据逻辑 } ) .store(in: &subscriptions) // 直接用Combine的标准语法 } }
为什么这个方案可行?
所有对underlyingSet的读写操作都被锁保护起来了,同一时间只有一个线程能访问它,彻底避免了数据竞争。编译器现在可以明确验证这个类的线程安全性,所以你完全可以去掉@unchecked Sendable,直接让类符合Sendable协议。
而且这个方案的性能开销极小,OSUnfairLock是苹果专门设计的轻量级锁,比用DispatchQueue串行队列的开销低很多,完全不用担心对性能造成影响。
内容来源于stack exchange
相关产品推荐
相关产品推荐

