Environment 참조 SwiftUI에서 @StateObject ViewModel의 의존 관계
StateObject
초기 값이 필요하지만 일반적으로 해당 시점Environment
에서는 정확한 값을 읽을 수 없습니다.이 글에서 소개한
LazyStateObject
를 이용하면 그 문제를 해결할 수 있다👌사용
LazyStateObject
의 샘플 코드는 다음과 같다.sample.swift
import SwiftUI
struct Dependency: DynamicProperty {
@Environment(\.networkClient) var networkClient
@Environment(\.storageClient) var storageClient
}
@MainActor
class ViewModel: ObservableObject {
let dependency: Dependency
@Published var count = 0
init(dependency: Dependency) {
self.dependency = dependency
}
func increment() async {
count = await dependency.networkClient.fetchCount(...)
}
}
struct ContentView: View {
@LazyStateObject(dependency: Dependency(), { dependency in
ViewModel(dependency: dependency)
})
var viewModel
var body: some View {
VStack {
// このタイミングで初回アクセスが走るため適切な値を持って初期化処理が行われる
Text("\(viewModel.count)")
Button(action: increment) {
Text("increment")
}
}
}
func increment() {
Task {
await viewModel.increment()
}
}
}
중점 일치DynamicProperty이렇게 하면 렌더 트리와 연관된 객체
Environment
등을 참조할 수 있습니다.응용 프로그램이 시작될 때 의존성을 주입하는 것이 좋다
또한 미리보기 시 모델 등을 주입할 수 있다
app.swift
import SwiftUI
@main
struct App: SwiftUI.App {
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.networkClient, NetworkClientImpl())
.environment(\.storageClient, StorageClientImpl())
}
}
}
그러나Environment
단일 테스트 시 임의의 설치를 주입할 수 없기 때문에 아래에 소개된 OverridableEnvironment
를 이용하여!
ViewModel(networkClient: ..., storageClient: ...)
같은 이니셜은 앞으로 대응할 필요가 없지만 의존성이 증가하면 개작하는 시간이 늘어난다💧샘플 코드에 대해 다음과 같은 변경을 진행하다
struct Dependency: DynamicProperty {
+ @OverridableEnvironment(\.networkClient) var networkClient
+ @OverridableEnvironment(\.storageClient) var storageClient
- @Environment(\.networkClient) var networkClient
- @Environment(\.storageClient) var storageClient
}
이렇게 하면 테스트 코드를 아래의 모쿠 등의 설치로 바꿀 수 있다import XCTest
class SampleTests: XCTestCase {
@MainActor
func test() async {
var deps = Dependency()
deps.networkClient = ...
deps.storageClient = ...
let viewModel = ViewModel(dependency: deps)
await viewModel.increment()
XCTAssertEqual(viewModel.count, ...)
}
}
소스 코드
LazyStateObject.swift
@propertyWrapper
public struct LazyStateObject<ObjectType, Dependency>: DynamicProperty
where ObjectType: ObservableObject {
public var wrappedValue: ObjectType {
if let object = holder.object {
return object
}
let newObject = initializer(dependency)
holder.object = newObject
return newObject
}
@StateObject private var holder = ObjectHolder<ObjectType>()
private let dependency: Dependency
private let initializer: (Dependency) -> ObjectType
public init(dependency: Dependency, _ initializer: @escaping (Dependency) -> ObjectType) {
self.dependency = dependency
self.initializer = initializer
}
}
private final class ObjectHolder<ObjectType>: ObservableObject
where ObjectType: ObservableObject {
var object: ObjectType? {
willSet {
cancellable = newValue?.objectWillChange
.sink(receiveValue: { [weak self] _ in
self?.objectWillChange.send()
})
objectWillChange.send()
}
}
private var cancellable: Cancellable? {
willSet { cancellable?.cancel() }
}
}
OverridableEnvironment.swift#if DEBUG
@propertyWrapper
public struct OverridableEnvironment<Value>: DynamicProperty {
public var wrappedValue: Value {
get { stateOverrideValue ?? overrideValue ?? environment.wrappedValue }
set {
overrideValue = newValue
stateOverrideValue = newValue
}
}
private var environment: Environment<Value>
private var overrideValue: Value?
@State private var stateOverrideValue: Value?
public init(_ keyPath: KeyPath<EnvironmentValues, Value>) {
environment = Environment(keyPath)
}
public mutating func reset() {
overrideValue = nil
stateOverrideValue = nil
}
}
extension OverridableEnvironment: Sendable where Value: Sendable {}
#else
// テスト以外では通常の方法で値を注入すれば良いので DEBUG 以外では標準の `Environment` として振る舞う
public typealias OverridableEnvironment = Environment
#endif
Reference
이 문제에 관하여(Environment 참조 SwiftUI에서 @StateObject ViewModel의 의존 관계), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/swiftty/articles/20220504-swiftui-state-object-environment텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)