맛동산이

ReactorKit ) Transform에 대하여 본문

앱/Swift

ReactorKit ) Transform에 대하여

진ddang 2024. 5. 14. 09:39

transform()

transform() transforms each stream. There are three transform() functions:

func transform(action: Observable<Action>) -> Observable<Action>
func transform(mutation: Observable<Mutation>) -> Observable<Mutation>
func transform(state: Observable<State>) -> Observable<State>

Implement these methods to transform and combine with other observable streams. For example, transform(mutation:) is the best place for combining a global event stream to a mutation stream. See the Global States section for details.

These methods can be also used for debugging purposes:

func transform(action: Observable<Action>) -> Observable<Action> { return action.debug("action") // Use RxSwift's debug() operator }


공식문서에는 다음과 같이 나와있습니다.

먼저 transform이라는 함수는 3가지가 존재한다.

  • func transform(action: Observable<Action>) -> Observable<Action>
  • func transform(mutation: Observable<Mutation>) -> Observable<Mutation>
  • func transform(state: Observable<State>) -> Observable<State>

이 함수에 대해서 설명하자면, 이러한 함수를 추가하는것으로 다른 observable stream과 결합할수 있다.

이게 무슨말이냐 하면, 어떠한 글로벌 이벤트 스트림을 보고 있다가, 이를 mutation stream으로 반환할수 있다는 말이다.

여기에서 말하는 Global Stream이란 무엇일까?

Global Stream

global Stream이란, 일반적으로 reactorkit의 reactor에서는 Action → Mutation → reduce ( State방출) 의 flow를 사용하게 되고 여기에서 State의 변경이 일어나면 이를 방출해주고 구독하고 있던 view에서 변경해주는 플로우를 가지고 있다.

하지만 여기에서 말하는 이러한 flow는 Global Stream이 아니다.

위에서 말하는 Global Stream이란, 전역적이거나, 혹은 해당 Reactor에서 가지고 있는 Subject와 같은 객체들을 의미한다 ( rxSwift의 subject )

따라서 behaviorSubject, 또는 publishSubject와 같은 이벤트를 방출하는 subject를 의미함

그래서 이러한 값들이 변경이 되면, transform을 사용하게 되는것이다.

예시를 보면 다음과 같다.

var calculatedAmount: PublishedSubject<APIResult> = PublishedSubject()

enum Action { case requestCalculate(Decimal) }
enum Mutation { updateTargeAmount(Decimal) }

func mutation() { 
	case let .requestCalculate(amount):
		return calculate(amount)
}

func calculate(amount: Decimal) { 네트워크 통신 이후, calculatedAmount.onNext(result) }

func transform(mutation: Observable<Mutation> ) -> Observable<Mutation> {
	let calculate = calculateSubject.flatMapLatest { response -> Observable<Mutation> in 
	let success = response.success...
	let failure = response.failure...
	return Observable.merge([success, failure])
}

이러한 대충의 상황이 있다고 했을때

  1. requestCalculate라는 액션이 들어오고
  2. calculate라는 네트워크 통신을 사용하게 된다.

여기에서 기존의 네트워크 통신을 하면서 값을 바로 방출하게 되면, 여러 네트워크 통신이 중첩적으로 쌓이게되고, 앞의 통신의 결과가 도착하지 않았는데 비동기기 때문에 중간의 이상한 값이 방출될 가능성이 존재한다.

(마지막 결과값만을 보고싶은 데 말이지…)

그래서 이때 이러한 네트워크 값들을 깡그리 무시하고 마지막에 들어온 네트워크 값만 사용하고 싶기때문에 이를 flatMapLatest로 감싸주고 싶은것인데, 이러한 값들을 PublishedSubject에 큐처럼 쌓아두고

이를 flatMapLatest를 사용해주어서 마지막 값만이 나도오도록 처리해주는것이다.

이때 PublishedSubject가 Global State가 되고,

이러한 Global Stream가 이벤트를 방출시킬때 transform이 동작하게 되는 과정인 것이다.


정리하자면

transform은 globalStream(behaviorSubject와 같은) 

해당 subject에 이벤트가 방출되면 transform이 실행된다. 

이때 transfrom의 종류는 3개가 있다. 

  • transform → State는 현재 State와 새로운 state를 결합해서 방출가능.
  • transform → Action은 현재 Action과 새로운 Action를 결합해서 방출가능.
  • transform -> Mutation 현재 Muation과 새로운 Mutation를 결합해서 방출가능.

이때 결합하는 State나 mutation 또는 action이 굳이 merge는 아니여도 된다. 

어쨋거나, 어떠한 상태를 방출한다는것이 중요함. (state, action, mutation) 3종류 중에 한개를.


참고자료

https://github.com/ReactorKit/ReactorKit?tab=readme-ov-file#transform

https://eunjin3786.tistory.com/147

반응형