如何创建只读的CurrentValueSubject,仅支持内部私有调用send方法?
只读CurrentValueSubject实现方案
完全可以实现你要求的能力,这也是Combine框架下库模块开发的常见需求,核心思路是通过访问控制隔离可变的读写能力,仅对外暴露只读接口,以下是两种成熟的实现方案:
方案1:拆分暴露Publisher与value属性(最常用)
将可变的CurrentValueSubject作为私有属性持有,对外分别暴露可订阅的Publisher和只读的当前值属性,实现代码如下:
import Combine public class LibraryService { // 私有持有可变Subject,仅内部可调用send方法修改值 private let statusSubject = CurrentValueSubject<ServiceStatus, Error>(.idle) // 对外暴露的可订阅Publisher,外部仅可监听不可修改 public var statusPublisher: AnyPublisher<ServiceStatus, Error> { statusSubject.eraseToAnyPublisher() } // 对外暴露的只读当前值属性 public var currentStatus: ServiceStatus { statusSubject.value } // 内部私有方法更新值,外部无权限调用 private func updateStatus(_ newStatus: ServiceStatus) { statusSubject.send(newStatus) } }
这个方案的优势是实现简单无额外封装,编译层面保证权限隔离,没有运行时额外开销。
方案2:封装只读结构体(用法更贴近原生Subject)
如果你希望对外暴露的对象和原生CurrentValueSubject用法一致,既可以直接调用sink订阅,也可以直接读取value属性,可以自定义一个只读封装类型:
import Combine public struct ReadOnlyCurrentValueSubject<Output, Failure: Error>: Publisher { private let underlyingSubject: CurrentValueSubject<Output, Failure> /// 对外只读的当前值 public var value: Output { underlyingSubject.value } public init(wrapping subject: CurrentValueSubject<Output, Failure>) { self.underlyingSubject = subject } /// 实现Publisher协议,支持直接订阅 public func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input { underlyingSubject.receive(subscriber: subscriber) } }
使用时只需对外暴露这个封装后的实例即可:
public class LibraryService { private let statusSubject = CurrentValueSubject<ServiceStatus, Error>(.idle) /// 对外暴露的只读Subject,支持直接订阅和读值,无send能力 public let status: ReadOnlyCurrentValueSubject<ServiceStatus, Error> public init() { status = ReadOnlyCurrentValueSubject(wrapping: statusSubject) } private func updateStatus(_ newStatus: ServiceStatus) { statusSubject.send(newStatus) } }
注意事项
不要直接对外返回未擦除类型的CurrentValueSubject,哪怕是声明为any Publisher类型也存在风险,外部可以通过强制类型转换拿到可变Subject实例调用send方法,使用eraseToAnyPublisher或者自定义只读封装类型都可以避免这个问题。
内容的提问来源于stack exchange,提问作者Geri Borbás
相关产品推荐
相关产品推荐

