我有一个名为 UserDefaults 的第一个 @property 包装器@UserDefault(key: .isSignedIn, defaultValue: false) var isSignedIn: Bool
。通过这种方式使用它作为发布者可以正常工作,$isSignedIn
以在单独使用时更新我的 SwiftUI 视图。但我想在视图模型中设置依赖项。
现在,为了保存 UserDefaults 值,我创建了一个依赖集作为 @propertyWrapper 调用@Injection(\.localDataManager) var localDataManager
,也将在单元测试中使用与名为的键关联的下标解决方案InjectedDependency[\.localDataManager] = LocalDataManagerMock()
问题是LocalDataManager
该类不是一个@ObservableObject
并且不会随着时间的推移更新我的 SwiftUI 视图。
- 如何
LocalDataManager
使用@ObservableObject
这个InjectedDependency
单元测试解决方案?
带有视图模型的 SwiftUI 视图:
import SwiftUI
struct ContentView: View {
@StateObject var viewModel = DataViewModel()
var body: some View {
VStack(spacing: 16) {
Text(viewModel.localDataManager.isSignedIn.description)
Button(action: { viewModel.localDataManager.isSignedIn.toggle() }) {
Text("Toggle boolean button")
}
}
}
}
class DataViewModel: ObservableObject {
@Injection(\.localDataManager) var localDataManager
}
我想用作@ObservableObject
:
import Foundation
protocol LocalDataManagerProtocol {
var isSignedIn: Bool { get set }
}
final class LocalDataManager: LocalDataManagerProtocol {
@UserDefault(key: .isSignedIn, defaultValue: false)
var isSignedIn: Bool
}
@propertyWrapper
struct Injection<T> {
private let keyPath: WritableKeyPath<InjectedDependency, T>
var wrappedValue: T {
get { InjectedDependency[keyPath] }
set { InjectedDependency[keyPath] = newValue }
}
init(_ keyPath: WritableKeyPath<InjectedDependency, T>) {
self.keyPath = keyPath
}
}
@UserDefault
代码:
import Combine
import SwiftUI
enum UserDefaultsKey: String {
case isSignedIn
}
protocol UserDefaultsProtocol {
func object(forKey defaultName: String) -> Any?
func set(_ value: Any?, forKey defaultName: String)
}
extension UserDefaults: UserDefaultsProtocol {}
@propertyWrapper
struct UserDefault<Value> {
let key: UserDefaultsKey
let defaultValue: Value
var container: UserDefaultsProtocol = UserDefaults.standard
private let publisher = PassthroughSubject<Value, Never>()
var wrappedValue: Value {
get {
return container.object(forKey: key.rawValue) as? Value ?? defaultValue
}
set {
container.set(newValue, forKey: key.rawValue)
publisher.send(newValue)
}
}
var projectedValue: AnyPublisher<Value, Never> {
publisher.eraseToAnyPublisher()
}
}
InjectedDependency
单元测试的解决方案:
// Use in Unit Test code in SetUp()
// `InjectedDependency[\.localDataManager] = LocalDataManagerMock()`
protocol InjectedKeyProtocol {
associatedtype Value
static var currentValue: Self.Value { get set }
}
struct InjectedDependency {
private static var current = InjectedDependency()
static subscript<K>(key: K.Type) -> K.Value where K: InjectedKeyProtocol {
get { key.currentValue }
set { key.currentValue = newValue }
}
static subscript<T>(_ keyPath: WritableKeyPath<InjectedDependency, T>) -> T {
get { current[keyPath: keyPath] }
set { current[keyPath: keyPath] = newValue }
}
var localDataManager: LocalDataManagerProtocol {
get { Self[LocalDataManagerKey.self] }
set { Self[LocalDataManagerKey.self] = newValue }
}
}
private struct LocalDataManagerKey: InjectedKeyProtocol {
static var currentValue: LocalDataManagerProtocol = LocalDataManager()
}