Swift 6中从Actor协议暴露可监听属性及值变化监听的问题咨询
我最近在Swift 6环境下碰到了个关于Actor属性监听的棘手问题——想在一个Actor里暴露一个可监听的布尔值,既要符合Actor的并发隔离要求,又能让外部(比如ViewModel)监听到值的变化。最开始我想用Combine的@Published来实现,结果踩了不少坑,下面把我的问题和摸索出的解决思路跟大家聊聊:
最初的尝试(Combine + Publisher)
我先定义了一个Actor协议,里面要求暴露Published<Bool>.Publisher,然后让Actor实现这个协议:
protocol MyServiceProtocol: Actor { var myValuePublisher: Published<Bool>.Publisher { get } } actor MyService: MyServiceProtocol { @Published private(set) var myValue = false var myValuePublisher: Published<Bool>.Publisher { $myValue } // 业务逻辑中修改myValue的方法 func modifyValue() { myValue.toggle() } }
然后在Main Actor的ViewModel里,我尝试了两种监听方式:一种是用Async Stream遍历values,另一种是用Combine的sink:
@MainActor final class MyViewModel { private let myService: MyServiceProtocol = MyService() func testMyService() { // 方式1:用Async Stream监听 Task { for await newValue in await myService.myValuePublisher.values { print("New value :", newValue) } } // 方式2:用Combine的sink监听 let cancellable = await myService.myValuePublisher.sink { newValue in print("New value :", newValue) } } }
遇到的编译错误
结果两种方式都报了编译错误,完全走不通:
- Async Stream方式:
Non-sendable type 'Published<Bool>.Publisher' of property 'myValuePublisher' cannot exit actor-isolated context
核心原因是Published.Publisher不是Sendable类型,没法安全地从Actor隔离的上下文传递出去。 - Combine Sink方式:
Actor-isolated property 'myValuePublisher' can not be referenced from the main actor
Actor的隔离属性不允许直接从Main Actor上下文访问,而Combine的Publisher模型无法适配这种隔离规则。
简单来说就是:Combine的Publisher完全适配不了Swift 6的Sendable和Actor隔离要求。
改用AsyncStream的解决方案
后来查了些思路,发现应该放弃暴露Combine Publisher,转而用Swift原生的AsyncStream来实现属性监听。下面是我调整后的实现:
1. 调整协议定义
把协议改成暴露当前属性值,以及返回AsyncStream<Bool>的方法:
protocol MyServiceProtocol: Actor { var myValue: Bool { get } func myValueStream() -> AsyncStream<Bool> }
2. 实现Actor逻辑
在Actor内部维护一个AsyncStream.Continuation,修改值的时候通过yield发送新值,同时在初始化流的时候先把当前值发给订阅者:
actor MyService: MyServiceProtocol { private(set) var myValue = false private var myValueContinuation: AsyncStream<Bool>.Continuation? deinit { // 销毁时结束流,避免内存泄漏 myValueContinuation?.finish() } // 业务逻辑中修改值的方法 func modifyValue() { myValue = !myValue myValueContinuation?.yield(myValue) } func myValueStream() -> AsyncStream<Bool> { AsyncStream { continuation in myValueContinuation = continuation // 先发送当前的初始值,确保订阅者能拿到最新状态 continuation.yield(myValue) } } }
3. ViewModel中监听流
在ViewModel里通过for await语法来订阅这个Async Stream:
@MainActor final class MyViewModel { private let myService: MyServiceProtocol = MyService() func testMyService() { Task { for await newValue in await myService.myValueStream() { print("New value: ", newValue) } } } }
总结
看起来在Swift 6的并发模型下,Combine的Publisher确实已经不太适配Actor的并发隔离要求了——毕竟Publisher本身不是Sendable类型,也很难完全符合Swift的并发安全规则。而用AsyncStream这类AsyncSequence的实现,是更贴合当前Swift并发设计的解决方案,既能满足Actor的隔离要求,又能实现属性变化的监听。
内容来源于stack exchange

