TCA - What is ViewStore
안녕하세요! 미니입니다. 이번에는 TCA로 프로젝트를 진행하면서 학습한 내용을 정리해보려고 합니다. 많은 것을 정리하는 것은 아니고, 개념적인 내용에 대해서 설명하려고 합니다. TCA를 조금 써보면 ViewStore라는 친구를 바로 만나게 되는데 내부적으로 어떤 동작이 일어나는 지 궁금해져서 학습한 내용을 공유해보려고 합니다.
우선 공식 문서 부터 조져보자구욧!
ViewStore는 상태의 변화와 액션 발생에 대해서 관찰할 수 있는 객체이다. 일반적으로 SwiftUI의 뷰나 UIView, UIViewController 타입에서 사용할 수 있다. 하지만, 상태를 관찰하고 액션을 전달하는 것이 적합한 곳이면 어디든 사용할 수 있다.
상태를 관찰할 수 있다는 말에서 Combine과 SwiftUI에서 많이 보던 친구라는 것이 느껴지시나요? 저는 ObservableObject 라는 친구가 생각 났습니다.
이렇게 여쭤본 이유는 이 친구이기 때문이에요. ViewStore는 다른 친구가 아니라 ObservableObject를 채택한 상속 불가능한 참조 타입입니다.
@dynamicMemberLookup
public final class ViewStore<ViewState, ViewAction>: ObservableObject {
public init<State>(
_ store: Store<State, ViewAction>,
observe toViewState: @escaping (_ state: State) -> ViewState,
removeDuplicates isDuplicate: @escaping (_ lhs: ViewState, _ rhs: ViewState) -> Bool
) {
self._send = { store.send($0, originatingFrom: nil) }
self._state = CurrentValueRelay(toViewState(store.state.value))
self._isInvalidated = store._isInvalidated
self.viewCancellable = store.state
.map(toViewState)
.removeDuplicates(by: isDuplicate)
.sink { [weak objectWillChange = self.objectWillChange, weak _state = self._state] in
guard let objectWillChange = objectWillChange, let _state = _state else { return }
objectWillChange.send()
_state.value = $0
}
}
}
ObservableObject라는 것은 납득이 가능하지만, dynamicMemberLookup이라는 어노테이션을 모르기에 찾아보려고 합니다.
dynamicMemberLookup
영어는 무적권 직독직해!! 동적 / 멤버 / 조회
라는 해석을 할 수 있겠죠? 좀 더 유식하게 말을 바꿔보시죠! 동적으로 값들을 조회할 수 있다.
와 같이 해석할 수 있습니다. 실제로 저희가 많이 사용하던 Subscript를 문자열이나 key값을 대괄호안에 작성해서 값을 얻게 되지만, dynamicMemberLookup
을 활용하게 되면, 도트를 통해서 접근할 수 있게 됩니다.
@dynamicMemberLookup
struct Person {
var name: String
var age: Int
subscript(key: String) -> String {
switch key {
case "info":
return "\(name) : \(age)"
default:
return "nothing"
}
}
subscript(dynamicMember key: String) -> String {
switch key {
case "member":
return "\(name) : \(age)"
default:
return "nothing"
}
}
}
var p = Person(name:"Sweet", age:17)
p["info"]
p.member
그렇다는 건 ViewStore는 내부적으로 키값을 생성하지 않고 도트를 통해서 속성에 접근하거나 값을 얻을 수 있다는 이야기가 되겠군요.
self.viewCancellable = store.state
.map(toViewState)
.removeDuplicates(by: isDuplicate)
.sink { [weak objectWillChange = self.objectWillChange, weak _state = self._state] in
guard let objectWillChange = objectWillChange, let _state = _state else { return }
objectWillChange.send()
_state.value = $0
}
생성자 내에서 이부분을 보게 되면, 어떻게 상태가 변경되었을 때 알려주는 거지?
와 같은 궁금증을 풀 수 있습니다. store에 생성되어 있는 state는 Publisher 타입이기 때문에 sink를 통해서 변경에 대한 이벤트를 받을 수 있습니다. 이를 통해서 내부적으로 값이 변경되었다는 이벤트를 다시 한번 방출 시키고 변경된 값을 기존의 값으로 업데이트하게 됩니다. 이런 방식을 통해서 저희는 View에서 액션을 전달하고, 상태가 변경될 때마다 뷰를 다시 그릴 수 있는 것입니다.
오늘은 글이 많이 짧지만, 다음에는 TCA를 통한 예제를 가지고 오려고 합니다. 기대해주세욧!!