J
[SwiftUI] SwiftUI + TCA: Environment vs. DependencyValues 본문
🧘 시작하기 전에…
저는 TCA를 최신 버전으로 다루다 보니 Environment보다는 DependencyValues를 사용하였는데요.
TCA를 학습하는 과정에서 Environment에 대한 글은 많았는데 DependencyValues에 대한 내용은 부족해서 어려움이 많았습니다.
또한 둘이 분명 외부 객체를 담당한다는 점에서 같은 역할을 하는 것 같은데 차이가 무엇인지 자세히 알고 싶어 직접 적어 봤습니다.
🔍 Environment와 DependencyValues의 차이점
Environment와 DependencyValues는 모두 의존성 주입(Dependency Injection)을 담당하는 개념이지만,
최근 TCA의 업데이트로 Environment의 역할이 DependencyValues로 대체되면서 두 방식 간에 중요한 차이점이 생겼습니다.
🔍 Environment (구버전 TCA)
기존의 TCA에서는 Environment 구조체를 활용하여 의존성을 직접 정의하고, 이를 Reducer에 주입하는 방식이었습니다.
✅ 특징
- Environment는 구조체로 정의되어, 네트워크 요청이나 데이터베이스 접근 같은 외부 의존성을 관리하는 역할.
- Reducer에서 Environment 타입을 직접 주입받아 사용.
- Store를 초기화할 때 직접 Environment 객체를 생성하여 주입해야 함.
- 테스트할 때도 별도의 Environment 객체를 만들어 주입해야 했음.
📌 예제 (구버전 TCA)
struct RandomCounterEnvironment {
var mainQueue: AnySchedulerOf<DispatchQueue>
var fetchRandomNumber: (Int) -> Effect<Int, Never>
}
let appReducer = Reducer<RandomCounterState, RandomCounterAction, RandomCounterEnvironment> { state, action, environment in
switch action {
case .increment:
state.count += 1
return .none
case .decrement:
state.count -= 1
return .none
case .fetchRandomNumber:
return environment.fetchRandomNumber(state.count)
.receive(on: environment.mainQueue)
.map(RandomCounterFeature.Action.randomNumberResponse)
.eraseToEffect()
case .randomNumberResponse(let number):
state.count = number
return .none
}
}
- 이전에는 Reducer에 Environment 타입을 명시적으로 포함해야 했기 때문에, 의존성이 없더라도 Environment 타입을 할당해야 했음
🔍 DependencyValues (최신 TCA)
새로운 TCA에서는 Reducer Protocol을 따르게 되면서, DependencyValues를 활용하는 방식으로 변경되었습니다.
✅ 특징
- DependencyValues는 글로벌 의존성 관리 시스템으로, Reducer 내부에서 의존성을 직접 불러와 사용할 수 있음.
- 의존성을 DependencyKey를 사용하여 전역적으로 등록하고, 필요한 Reducer에서 @Dependency 프로퍼티 래퍼를 사용해 가져 옴.
- Reducer가 Environment에 의존하지 않기 때문에 보다 독립적으로 유지보수할 수 있음.
- Store를 초기화할 때 의존성을 직접 주입할 필요가 없음.
📌 예제 (최신 TCA)
1) 의존성 정의 (DependencyKey 사용)
struct RandomCounterClient {
var fetchRandomNumber: () async throws -> Int
}
extension RandomCounterClient {
// API 통신
static let live = RandomCounterClient(fetchRandomNumber: {
let url = URL(string: "<http://www.randomnumberapi.com/api/v1.0/random?min=1&max=1000&count=1>")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Int].self, from: data).first ?? 0
})
}
private enum RandomCounterClientKey: DependencyKey {
static let liveValue = RandomCounterClient.live
}
2) 의존성을 DependencyValues에 등록
extension DependencyValues {
var randomNumberClient: RandomNumberClient {
get { self[RandomNumberClientKey.self] }
set { self[RandomNumberClientKey.self] = newValue }
}
}
3) Reducer에서 @Dependency로 사용
struct CounterFeature: Reducer {
@Dependency(\\.randomCounterClient) var client // Dependency 주입
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .increment:
state.count += 1
return .none
case .decrement:
state.count -= 1
return .none
case .fetchRandomNumber:
return .run { send in
do {
let number = try await client.fetchRandomNumber()
await send(.randomNumberResponse(number))
} catch {
print("랜덤 넘버 가져오기 실패: \\(error)")
}
}
case .randomNumberResponse(let number):
state.count = number
return .none
}
}
}
}
4) Store 생성 시 별도로 의존성 주입할 필요 없음
@main
struct TCAExampleApp: App {
var body: some Scene {
WindowGroup {
RandomCounterView(
store: Store(
initialState: RandomCounterFeature.State(),
reducer: { RandomCounterFeature() }
)
)
}
}
}
🔍 Environment vs. DependencyValues 비교
Environment (구버전 TCA) | DependencyValues (최신 TCA) | |
의존성 관리 방식 | Environment 구조체를 별도로 정의하여 관리 | DependencyKey 프로토콜을 활용하여 전역적으로 등록 |
Reducer 내 의존성 주입 | Reducer<State, Action, Environment>의 세 번째 인자로 Environment 주입 | @Dependency(\\.key)를 사용하여 자동으로 가져옴 |
Store 초기화 시 의존성 주입 | Store를 생성할 때 직접 Environment 객체를 주입해야 함 | 별도로 의존성을 주입할 필요 없음 |
코드의 간결함 | Environment 정의가 필요하고, Store 초기화 시 매번 전달해야 함 | DependencyValues에 한 번 등록하면 이후 Reducer에서 직접 가져다 사용 가능 |
의존성이 없는 Reducer | Environment가 필요 없어도 Reducer에서 명시적으로 선언해야 함 | 의존성이 없는 Reducer는 DependencyValues 없이 독립적으로 유지 가능 |
테스트 편의성 | Environment를 테스트 전용으로 만들어 Store에 주입해야 함 | TestStore에서 DependencyValues를 수정하여 테스트 환경을 간편하게 설정 가능 |
🧘 마치며…
사실 이렇게 봐도 큰 차이는 못 느끼겠는요…ㅜㅜ
앞으로 더 학습하며 내용을 보강해야 할 것 같습니다…
'SwiftUI' 카테고리의 다른 글
[SwiftUI] TCA 공식문서로 시작하기_Reducer (0) | 2024.08.20 |
---|---|
[SwiftUI] TCA 공식문서로 시작하기_Getting started (0) | 2024.08.20 |
[SwiftUI] SwiftUI + TCA: Effect? DependencyValues? (예제 포함) (0) | 2024.07.08 |
[SwiftUI] SwiftUI + TCA: Client 훑고 가기… (0) | 2024.07.05 |
[SwiftUI] SwiftUI + TCA: 최신 버전 TCA의 동작 과정 (예제 포함) (0) | 2024.07.05 |