如何自动生成暴露@Published属性的只读Swift协议?
问题:自动生成包含@Published属性只读Publisher的协议
我正在进行重构工作,需要限制模型对象的写权限。现有模型是带有@Published属性的引用类型:
public final class Model { public enum State { /* Some cases here. */ } @Published public var state: State }
当前通过ViewModelAPI协议暴露视图模型的只读访问权限,但ViewModel仍能修改Model的属性,且手动维护这类协议成本极高(Model更新时需同步修改协议)。希望自动生成协议,将所有@Published属性转为只读的AnyPublisher,想知道动态成员查找是否可行,或是需要脚本生成。
可行解决方案
1. 动态成员查找(Dynamic Member Lookup)
可以用@dynamicMemberLookup实现通用只读包装器,自动暴露所有@Published属性的Publisher,无需手动编写协议。
示例代码:
import Combine @dynamicMemberLookup public class ReadOnlyModelWrapper<Model: AnyObject> { private let model: Model public init(model: Model) { self.model = model } // 处理@Published属性,返回对应的AnyPublisher public subscript<Value>(dynamicMember keyPath: KeyPath<Model, Published<Value>.Publisher>) -> AnyPublisher<Value, Never> { model[keyPath: keyPath].eraseToAnyPublisher() } // 可选:支持普通只读属性的访问 public subscript<Value>(dynamicMember keyPath: KeyPath<Model, Value>) -> Value { model[keyPath: keyPath] } }
使用方式:
let model = Model() let readOnlyWrapper = ReadOnlyModelWrapper(model: model) // 直接通过动态成员访问Publisher readOnlyWrapper.$state.sink { state in // 处理状态更新逻辑 }
优势:无需维护额外协议,Model更新时自动适配;缺点:编译时类型检查较弱,IDE自动补全体验略差。
2. 代码生成工具(Sourcery/SwiftGen)
如果需要强类型的协议,推荐用代码生成工具自动生成对应代码,以Sourcery为例:
- 编写Sourcery模板,扫描目标类的
@Published属性,生成包含对应AnyPublisher只读属性的协议及扩展。 - 配置Sourcery在编译前自动执行,生成代码文件。
简化版模板示例:
{% for type in types.classes where type.annotations.contains("GenerateReadOnlyProtocol") %} public protocol {{ type.name }}ReadOnly { {% for property in type.properties where property.attributes.contains("Published") %} var {{ property.name }}Publisher: AnyPublisher<{{ property.type.genericArguments.first.name }}, Never> { get } {% endfor %} } extension {{ type.name }}: {{ type.name }}ReadOnly { {% for property in type.properties where property.attributes.contains("Published") %} public var {{ property.name }}Publisher: AnyPublisher<{{ property.type.genericArguments.first.name }}, Never> { ${{ property.name }}.eraseToAnyPublisher() } {% endfor %} } {% endfor %}
给Model添加注解:
// sourcery: GenerateReadOnlyProtocol public final class Model { public enum State { /* ... */ } @Published public var state: State }
Sourcery会自动生成ModelReadOnly协议,ViewModel依赖该协议而非原始Model,既保证强类型安全,又无需手动维护协议内容。
3. 手动协议的简化写法
如果不想引入外部工具,可通过扩展自动实现协议,降低维护成本:
public protocol ModelReadOnly { var statePublisher: AnyPublisher<Model.State, Never> { get } } extension Model: ModelReadOnly { public var statePublisher: AnyPublisher<State, Never> { $state.eraseToAnyPublisher() } }
Model更新时,仅需在协议中添加新的Publisher属性,并在扩展中实现对应逻辑即可,维护成本远低于原ViewModel方案。
内容的提问来源于stack exchange,提问作者Kurt Horimoto
相关产品推荐
相关产品推荐

