Today we're going to talk about a Pulse in ReactorKit.
Added to version 3.1.0 and partially modified from version 3.2.0, the most recent version of the current (2022.04.08).
3.1.0
... Introduce Pulse 📡 (@tokijh)
3.2.0 Latest
...
Make public valueUpdatedCount on Pulse by @tokijh in #196
In fact, Pulse is currently being used for in-company projects, and I'm writing this because I'm not sure what this means.I think we can find out one by one.😁
Well... what is it? This looks similar to distinctUntilChanged operator in RxSwift in my think.
and I took the code and ran it in xcode.
Well, there's an error...( An error-free modified code is at the end.)
If so, we'll have no choice but to look at the following documents:
// 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
Looking at the // Cases, perhaps something similar to 'distinctUntilChanged' is correct.
//// 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 }}
The code for Pulse is as above. Genetic structure and PropertyWrapper has characteristics. If you want to know more about PropertyWrapper, you can look at the official document
Actually, I didn't get it at first, but the important part is var value and didSet.Every time the value changes, it does something specific. The work is as follows.
Whenever the value changes, the count valueUpdatedCount is +1. And if the valueUpdatedCount is UInt.max, we are assigning UInt.min back to the valueUpdatedCount.That's all. Shall we move on?
//// 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) }}
If you look at the code above, that's added a method func pulse as an extension to the Reactor. and used distinctUntilChanged in operator in RxSwift.
The operator is the one that receives the keySelector as a parameter among the four supported by RxSwift.
So if you summarize it here, Pulse emits events, but only when the values of the variables valueUpdatedCount declared inside change.
So when will the value of valueUpdatedCountchange? As mentioned above, this is when value changes.
The official document of ReactorKit provides additional explanations and examples as below.
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)
The most important part is if the new value is assigned. That is, the stream does not emit events unless a new value is assigned.
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) }
The test is kindly annotated. It says // same count because no new values are assigned..
i.e. the value of valueUpdatedCount is not incremented because we didn't assign a new value to the value like state.value = 2 , and consequently Pulse will not emit any events.
So, Pulse, how to use it? Again, as kindly described in the documentation, attach @Pulse attribute to State and import it in the same way as reactor.pulse(\.$alertMessage) inside func bind(reactor:).
Insert messagePulse into oldMessagePulse and assign a new value to the value of the messagePulse.
If you do that, the values of oldMessagePulse and messagePulse are the same, but valueUpdatedCount is +1 as the value is assigned, so the valueUpdatedCount of oldMessagePulse and messagePulse is not the same.
Above, we learned about Pulse in Reactorkit. I was a little confused because I didn't know what it means to use it, but I hope that people who read this article will find it helpful. 😊