"음, 그러면 일단 코드를 복붙에서 실행해보자!" 라는 생각이 들어서 그대로 코드를 가져와서 실행해보았습니다.
음 에러가 나는군요...( 에러가 나지않는 수정된 코드는 마지막에 있습니다. )
그렇다면 별 수 없이 다음 문서 내용을 보겠습니다.
// ReactorprivatefinalclassMyReactor:Reactor {structState {@Pulsevar alertMessage: String? }funcmutate(action: Action) -> Observable<Mutation> {switch action {caselet .alert(message):return Observable.just(Mutation.setAlertMessage(message)) } }funcreduce(state: State, mutation: Mutation) -> State {var newState = stateswitch mutation {caselet .setAlertMessage(alertMessage): newState.alertMessage = alertMessage }return newState } }// View reactor.pulse(\.$alertMessage) .compactMap { $0 } // filter nil .subscribe(onNext: { [weak self] (message:String)in self?.showAlert(message) }) .disposed(by: disposeBag)// Cases reactor.action.onNext(.alert("Hello"))// showAlert() is called with `Hello` reactor.action.onNext(.alert("Hello"))// showAlert() is called with `Hello` reactor.action.onNext(.doSomeAction)// showAlert() is not called reactor.action.onNext(.alert("Hello"))// showAlert() is called with `Hello` reactor.action.onNext(.alert("tokijh"))// showAlert() is called with `tokijh` reactor.action.onNext(.doSomeAction)// showAlert() is not called
음, 밑에 Cases를 보니 정확히는 뭔지 모르겠어도 일단 distinctUntilChanged 같은? 비슷한? 녀석은 맞는 것 같더라구요. 그런데, 아직도 확실히 이해는 잘 안갔습니다. 그래서 코드를 뜯어보기로 했죠.(마침 짧기도 해서..^^;)
//// Pulse.swift// ReactorKit//// Created by tokijh on 2021/01/11.//@propertyWrapperpublicstructPulse<Value> {publicvar value: Value {didSet { self.riseValueUpdatedCount() } }publicinternal(set)var valueUpdatedCount =UInt.minpublicinit(wrappedValue: Value) { self.value= wrappedValue }publicvar wrappedValue: Value {get { return self.value }set { self.value= newValue } }publicvar projectedValue: Pulse<Value> {return self }privatemutatingfuncriseValueUpdatedCount() { self.valueUpdatedCount &+=1 }}
Pulse의 코드는 위와 같습니다. 제네릭 구조체에 PropertyWrapper 특성을 가지고 있습니다. PropertyWrapper 에 대해서 더 알고싶으시면 공식문서에서 보시면 될 것 같아요.
사실, 처음에는 저도 이해가 잘안갔는데 중요한 부분은 var value 의 didSet 부분입니다. value 값이 바뀔때마다 어떤 특정 작업을 해주고 있죠. 그 작업은 아래와 같구요.
value값이 바뀔 때마다 valueUpdatedCount라는 count값을 +1 해주고 있습니다. 그리고 만약에 valueUpdatedCount의 값이 UInt.max라면 valueUpdatedCount값에 다시 UInt.min 을 할당해주고 있구요. 여기서는 일단 이 정도만 파악하면 충분합니다. 다음으로 넘어가볼까요?
//// Reactor+Pulse.swift// ReactorKit//// Created by 윤중현 on 2021/03/31.//extensionReactor {publicfuncpulse<Result>(_transformToPulse: @escaping (State) throws-> Pulse<Result>) -> Observable<Result> {return self.state.map(transformToPulse).distinctUntilChanged(\.valueUpdatedCount).map(\.value) }}
위 코드를 보면, Reactor에 extension으로 func pulse 라는 메소드를 추가했습니다. 내부 구현을 볼까요 ? 아까도 말했다시피 distinctUntilChanged가 나오긴 하네요. 제 짐작이 약간?은 맞은 듯 합니다. 😅 그리고 해당 distinctUntilChanged 연산자는 RxSwift에서 지원하는 4가지 중에 keySelector를 매개변수로 받는 녀석입니다.
즉, 여기서 정리해보면 Pulse는 이벤트을 방출하기는 하되, Pulse 내부에 선언되어있는 변수 valueUpdatedCount 값이 바뀌어야만 이벤트를 방출한다 는 겁니다.
그럼 valueUpdatedCount 값이 언제 바뀔까요 ? 바로 위에 말했던 것처럼 value 값이 바뀔 때 입니다.
ReactorKit 공식문서에서는 아래와 같이 추가적인 설명과 예시를 들어주고 있어요.
Use when you want to receive an event only if the new value is assigned, even if it is the same value. like alertMessage (See follows or PulseTests.swift)
여기서 가장 중요한 부분은 if the new value is assigned 겠죠. 즉, 일반적으로 알고있는 같은 이벤트가 방출되면 걸러주는 distinctUntilChanged 와 달리 여기서는 그 조건이 value 값이 바뀌어서 valueUpdatedCount 값이 다르지 않으면 이벤트를 방출하지 않는다는 겁니다.
importXCTestimportRxSwift@testableimportReactorKitfinalclassPulseTests:XCTestCase {functestRiseValueUpdatedCountWhenSetNewValue() {// givenstructState {@Pulsevar value: Int=0 }var state =State()// when & thenXCTAssertEqual(state.$value.valueUpdatedCount, 0) state.value=10XCTAssertEqual(state.$value.valueUpdatedCount, 1)XCTAssertEqual(state.$value.valueUpdatedCount, 1)// same count because no new values are assigned. state.value=20XCTAssertEqual(state.$value.valueUpdatedCount, 2) state.value=20XCTAssertEqual(state.$value.valueUpdatedCount, 3) state.value=20XCTAssertEqual(state.$value.valueUpdatedCount, 4)XCTAssertEqual(state.$value.valueUpdatedCount, 4)// same count because no new values are assigned. state.value=30XCTAssertEqual(state.$value.valueUpdatedCount, 5) state.value=30XCTAssertEqual(state.$value.valueUpdatedCount, 6) }
테스트를 보면 친절히 주석이 적혀있어요. 주석을 보면 // same count because no new values are assigned. 라고 되어있죠.
즉, state.value = 2 와 같은 식으로 value 값에 새로운 값을 할당하지 않았기 때문에 valueUpdatedCount 값은 증가되지 않았고, 결과적으로 Pulse는 이벤트를 방출하지 않습니다.
그러면 Pulse, 사용은 어떻게 해야 할까요 ? 역시나 또 친절하게 문서에 나와있는 것처럼, State에 @Pulse 특성을 붙여주고 func bind(reactor:) 내부에서 reactor.pulse(\.$alertMessage) 같은 식으로 가져오면 됩니다.