如何让@propertyWrapper注入的LocalDataManager成为@ObservableObject?
改造方案
核心思路是在不改动原有依赖注入测试逻辑的前提下,通过ObservableObject协议桥接属性变更事件,具体实现步骤如下:
1. 调整LocalDataManagerProtocol协议,限定为类类型
// 仅添加AnyObject限定,原有属性定义不变 protocol LocalDataManagerProtocol: AnyObject { var isSignedIn: Bool { get set } }
2. 改造LocalDataManager遵守ObservableObject,桥接@UserDefault的变更事件
import Combine import Foundation final class LocalDataManager: ObservableObject, LocalDataManagerProtocol { @UserDefault(key: .isSignedIn, defaultValue: false) var isSignedIn: Bool private var cancellables = Set<AnyCancellable>() init() { // 把UserDefault的发布事件和当前类的objectWillChange绑定 $isSignedIn .receive(on: DispatchQueue.main) .sink { [weak self] _ in self?.objectWillChange.send() } .store(in: &cancellables) } } // 单元测试用的Mock类也遵守ObservableObject即可 final class LocalDataManagerMock: ObservableObject, LocalDataManagerProtocol { var isSignedIn: Bool = false // 测试时如果需要模拟属性变更,也可以按需求加@Published或者手动触发objectWillChange }
3. 改造视图模型,监听注入的localDataManager的变更事件
import Combine class DataViewModel: ObservableObject { @Injection(\.localDataManager) var localDataManager private var cancellables = Set<AnyCancellable>() init() { // 把注入的localDataManager的变更事件和当前视图模型的objectWillChange绑定 if let observableManager = localDataManager as? any ObservableObject { observableManager.objectWillChange .receive(on: DispatchQueue.main) .sink { [weak self] _ in self?.objectWillChange.send() } .store(in: &cancellables) } } }
效果说明
原有依赖注入的测试逻辑完全不受影响,单元测试时仍可以直接通过InjectedDependency[\.localDataManager] = LocalDataManagerMock()替换实现。当isSignedIn属性变更时,事件会沿着@UserDefault -> LocalDataManager.objectWillChange -> DataViewModel.objectWillChange传递,触发SwiftUI视图自动刷新。
可选优化(iOS 17+)
如果你的项目最低部署版本是iOS 17,可以直接使用Swift Observation框架替代Combine方案,代码更简洁:
- 给
LocalDataManager和LocalDataManagerMock加上@Observable宏,不需要再手动处理发布者 - 视图模型不需要添加任何监听代码,SwiftUI会自动跟踪
@Observable类的属性变更 - 原有依赖注入逻辑完全不变
内容的提问来源于stack exchange,提问作者Roland Lariotte
相关产品推荐
相关产品推荐

