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

如何创建只读的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 12:06:03