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

Swift泛型问题:如何存储任意类型Store数组并比较状态

嘿,这个问题我之前做状态管理时也碰到过,核心就是泛型协议的关联类型限制导致没法直接存数组,还有Equatable约束的冲突问题。咱们一步步来解决:

核心问题拆解

你遇到的两个关键坑:

  1. Store<Any>行不通,因为Any不遵循你的State协议(而State要求实现Equatable);
  2. 带关联类型的Store协议不能直接作为数组元素,Swift不允许把带Self或关联类型要求的协议当作具体类型使用。

不过别担心,有两种靠谱的解决方案,分别对应你“保留Equatable”和“不依赖Equatable”的需求:


方案1:用类型擦除(Type Erasure)统一存储任意Store

类型擦除是Swift处理带关联类型协议的标准操作,本质是用一个具体类包装所有符合协议的泛型实例,隐藏掉具体的关联类型。

步骤1:修正协议命名(避免冲突)

先把你的Store协议改个名字,避免和类名冲突,同时调整关联类型命名:

import RxSwift

protocol State: Equatable {}

// 重命名协议为StoreProtocol,避免和类名冲突
protocol StoreProtocol: class {
    associatedtype StateType: State
    var processor: PublishSubject<StateType> { get }
    var _state: StateType { get set }
    var state: StateType { get set }
    func initialState() -> StateType
    func flowable() -> Observable<StateType>
    func initialize()
}

// 保留原来的协议扩展逻辑
extension StoreProtocol {
    var state: StateType {
        get { return _state }
        set(value) {
            if value != state {
                _state = value
                processor.onNext(value)
            }
        }
    }
    
    func flowable() -> Observable<StateType> {
        return processor.startWith(state)
    }
    
    func initialState() -> StateType {
        return StateType.init()
    }
}

// 让原来的Store类遵循StoreProtocol
class Store<S: State>: StoreProtocol {
    typealias StateType = S
    
    private(set) var processor = PublishSubject<S>()
    var _initialState: S
    var _state: S
    
    init(initialState: S) {
        _initialState = initialState
        _state = initialState
    }
    
    func initialState() -> S {
        return _initialState
    }
    
    func initialize() {
        preconditionFailure("This method must be overridden")
    }
}

步骤2:实现类型擦除的AnyStore

创建AnyStore类,包装任意符合StoreProtocol的实例,对外暴露统一接口:

class AnyStore {
    private let _getState: () -> Any
    private let _setState: (Any) -> Void
    private let _getFlowable: () -> Observable<Any>
    private let _initialize: () -> Void
    private let _isStateEqual: (Any) -> Bool
    
    init<ConcreteStore: StoreProtocol>(_ store: ConcreteStore) {
        // 封装具体Store的操作
        _getState = { store.state }
        _setState = { newValue in
            guard let typedValue = newValue as? ConcreteStore.StateType else { return }
            store.state = typedValue
        }
        _getFlowable = { store.flowable().map { $0 as Any } }
        _initialize = { store.initialize() }
        // 封装Equatable比较逻辑
        _isStateEqual = { other in
            guard let otherTyped = other as? ConcreteStore.StateType else { return false }
            return store.state == otherTyped
        }
    }
    
    // 对外暴露统一的状态访问接口
    var state: Any {
        get { _getState() }
        set { _setState(newValue) }
    }
    
    // 对外暴露状态流
    func flowable() -> Observable<Any> {
        _getFlowable()
    }
    
    // 初始化方法
    func initialize() {
        _initialize()
    }
    
    // 状态比较方法
    func isStateEqual(to other: Any) -> Bool {
        _isStateEqual(other)
    }
}

步骤3:在Logger里使用AnyStore数组

现在你的Logger可以轻松存储任意类型的Store了:

class Logger {
    private var stores: [AnyStore] = []
    private let disposeBag = DisposeBag()
    
    func addStore(_ store: AnyStore) {
        stores.append(store)
    }
    
    // 示例:跟踪所有状态变化
    func trackAllStateChanges() {
        stores.forEach { store in
            store.flowable().subscribe(onNext: { newState in
                print("State changed to: \(newState)")
            }).disposed(by: disposeBag)
        }
    }
}

// 使用示例
struct AppState: State {
    var count: Int
}

let logger = Logger()
let appStore = Store(initialState: AppState(count: 0))
logger.addStore(AnyStore(appStore))
logger.trackAllStateChanges()

方案2:不依赖Equatable的状态比较

如果你的某些State类型很难遵循Equatable(比如包含闭包、资源引用等),可以用自定义比较逻辑替代Equatable:

步骤1:修改State协议,添加自定义比较方法

protocol State {
    // 自定义比较方法,替代Equatable的==
    func isEqual(to other: Self) -> Bool
}

// 让遵循Equatable的State自动实现isEqual方法
extension State where Self: Equatable {
    func isEqual(to other: Self) -> Bool {
        return self == other
    }
}

步骤2:修改Store类,支持自定义比较逻辑

class Store<S: State> {
    private var _initialState: S
    private var _state: S
    private var processor = PublishSubject<S>()
    // 允许注入自定义比较闭包,默认用State的isEqual方法
    private let stateComparator: (S, S) -> Bool
    
    init(initialState: S, stateComparator: @escaping (S, S) -> Bool = { $0.isEqual(to: $1) }) {
        _initialState = initialState
        _state = initialState
        self.stateComparator = stateComparator
    }
    
    var state: S {
        get { return _state }
        set(value) {
            // 用自定义比较替代==
            if !stateComparator(value, state) {
                _state = value
                processor.onNext(value)
            }
        }
    }
    
    // ... 其他方法不变
}

步骤3:结合类型擦除统一存储

和方案1一样,你可以创建AnyStore来包装这种自定义比较的Store,存入Logger的数组中。这种方式灵活性更高,适合复杂状态的场景。


总结

  • 如果你的State都能遵循Equatable,方案1的类型擦除是最直接的解决方式,既保留类型安全,又能统一存储;
  • 如果有无法遵循Equatable的State,方案2的自定义比较逻辑可以完美替代,同时也能结合类型擦除实现统一存储。

内容的提问来源于stack exchange,提问作者Francisco Durdin Garcia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:12:50