jay's devnote
  • README
  • 자료구조 & 알고리즘
    • 자료구조
    • 알고리즘
  • 디자인 패턴
    • 디자인 패턴 원칙, 요약, 분류
    • IS-A 와 HAS-A
    • 전략 패턴
    • 옵저버 패턴
    • 데코레이터 패턴
    • 팩토리 패턴
    • 싱글턴 패턴
    • 커맨드 패턴
    • 어댑터, 퍼사드 패턴
    • 템플릿 메소드 패턴
    • 반복자, 컴포지트 패턴
    • 상태 패턴
    • 프록시 패턴
    • 복합 패턴
  • WWDC
    • 2015, Building Better Apps with Value Types in Swift
    • 2015, Protocol-Oriented Programming in Swift
    • 2016, Understanding Swift Performance
    • 2016, Protocol and Value Oriented Programming in UIKit Apps
    • 2017, Engineering for Testability
    • 2018, High Performance Auto Layout
    • 2018, Testing Tips & Tricks
    • 2020, Advances in UICollectionView
    • 2020, Lists in UICollectionView
  • 패러다임
    • 객체지향 프로그래밍, SOLID 원칙
      • SRP, 단일 책임 원칙
      • OCP, 개방 폐쇄 원칙
      • LSP, 리스코프 치환 원칙
      • ISP, 인터페이스 분리 원칙
      • DIP, 의존성 역전 원칙
    • 만들면서 느껴보는 POP
    • Swift로 함수형 프로그래밍 시작하기
  • 아키텍쳐
    • ReactorKit
      • Pulse(EN)
      • Pulse(KR)
    • Coordinator Pattern
  • iOS
    • Safari로 웹뷰의 세션/쿠키 정보 확인하기
    • App Icon 동적으로 변경하기
    • WKDataDetectorTypes의 데이터 탐지
    • Xcode에서 메모리 누수 확인하기
    • 개발 인증서 관리하기
    • required init?(coder: NSCoder)
    • UIFontMetrics 와 UIFont.preferredFont
    • 제약조건을 줄여주는 UIStackView
    • UICollectionView.CellRegistration<Cell, Item>
  • Swift
    • Swift API Design Guidelines
    • 패턴 매칭
    • allSatisfy()
    • 생성자
    • 프로토콜의 동적 디스패치와 정적 디스패치
    • Swift 문법 정리
  • RxSwift
    • RxSwift 핸드북
    • Just, From, Of
    • withLatestFrom
  • SwiftUI
    • SwiftUI에서의 마크다운 문법
    • @State, @Binding
    • ObservableObject, @ObservedObject, @Published
    • @ObservedObject vs @StateObject
  • Git
    • Git gitignore
    • Github API Rate limit
    • GitKraken(깃크라켄) 활용하기
    • GitKraken으로 Git-flow 활용하기
  • Etc
    • Struct을 [String: Any]로 변환할 때, Encodable의 Extension을 사용 해야 하나요?
    • Podfile, Dependency Rule(SPM)
    • 맥으로 고정 IP 연결하는 방법
    • SwiftPlantUML으로 UML 다이어그램 쉽게 그리기
    • Playground 가 열리지 않는 오류 해결하기
    • CocoaPods 제거하기
  • Python
    • 파이썬과 스위프트 문법 비교
    • 파이썬과 스위프트 문법 요약
  • Firebase
    • Storage를 API처럼 사용해보기
    • RealTime Database를 API처럼 사용해보기
Powered by GitBook
On this page
  1. 아키텍쳐
  2. ReactorKit

Pulse(EN)

PreviousReactorKitNextPulse(KR)

Last updated 2 years ago

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.😁

First, let's look at the documents.

introduces Pulse like this.

Pulse has diff only when mutated To explain in code, the results are as follows.

Well... I see.I didn't get it.

"shall we look at the code?"

  var messagePulse: Pulse<String?> = Pulse(wrappedValue: "Hello tokijh")

  let oldMessagePulse: Pulse<String?> = message
  message = "Hello tokijh"

  oldMessagePulse != messagePulse // true
  oldMessagePulse.value == messagePulse.value // true

Well... what is it? This looks similar to 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:

  // Reactor
  private final class MyReactor: Reactor {
    struct State {
      @Pulse var alertMessage: String?
    }

    func mutate(action: Action) -> Observable<Mutation> {
      switch action {
      case let .alert(message):
        return Observable.just(Mutation.setAlertMessage(message))
      }
    }

    func reduce(state: State, mutation: Mutation) -> State {
      var newState = state

      switch mutation {
      case let .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
//
//  Pulse.swift
//  ReactorKit
//
//  Created by tokijh on 2021/01/11.
//

@propertyWrapper
public struct Pulse<Value> {

  public var value: Value {
    didSet {
      self.riseValueUpdatedCount()
    }
  }
  public internal(set) var valueUpdatedCount = UInt.min

  public init(wrappedValue: Value) {
    self.value = wrappedValue
  }

  public var wrappedValue: Value {
    get { return self.value }
    set { self.value = newValue }
  }

  public var projectedValue: Pulse<Value> {
    return self
  }

  private mutating func riseValueUpdatedCount() {
    self.valueUpdatedCount &+= 1
  }
}

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.

  private mutating func riseValueUpdatedCount() {
    self.valueUpdatedCount &+= 1
  }

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.
//

extension Reactor {
  public func pulse<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.

  public func distinctUntilChanged<Key: Equatable>(_ keySelector: @escaping (Element) throws -> Key)
      -> Observable<Element> {
      self.distinctUntilChanged(keySelector, comparer: { $0 == $1 })
  }

usually use is as follows.

  struct Human {
    let name: String
    let age: Int
  }

  let myPublishSubject = PublishSubject<Human>.init()

  myPublishSubject
    .distinctUntilChanged(\.name)
    .debug()
    .subscribe()
    .disposed(by: disposeBag)

  myPublishSubject.onNext(Human(name: "a", age: 1))
  myPublishSubject.onNext(Human(name: "a", age: 2))
  myPublishSubject.onNext(Human(name: "c", age: 3))

  //-> subscribed
  //-> Event next(Human(name: "a", age: 1))
  //-> Event next(Human(name: "c", age: 3))

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.

import XCTest
import RxSwift
@testable import ReactorKit

final class PulseTests: XCTestCase {
  func testRiseValueUpdatedCountWhenSetNewValue() {
    // given
    struct State {
      @Pulse var value: Int = 0
    }

    var state = State()

    // when & then
    XCTAssertEqual(state.$value.valueUpdatedCount, 0)
    state.value = 10
    XCTAssertEqual(state.$value.valueUpdatedCount, 1)
    XCTAssertEqual(state.$value.valueUpdatedCount, 1) // same count because no new values are assigned.
    state.value = 20
    XCTAssertEqual(state.$value.valueUpdatedCount, 2)
    state.value = 20
    XCTAssertEqual(state.$value.valueUpdatedCount, 3)
    state.value = 20
    XCTAssertEqual(state.$value.valueUpdatedCount, 4)
    XCTAssertEqual(state.$value.valueUpdatedCount, 4) // same count because no new values are assigned.
    state.value = 30
    XCTAssertEqual(state.$value.valueUpdatedCount, 5)
    state.value = 30
    XCTAssertEqual(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:).

  struct State {
    @Pulse var alertMessage: String?
  }

  // View
  reactor.pulse(\.$alertMessage)
    .compactMap { $0 } // filter nil
    .subscribe(onNext: { [weak self] (message: String) in
      self?.showAlert(message)
    })
    .disposed(by: disposeBag)

In conclusion, the official document above should be partially revised as below, right?

  var messagePulse: Pulse<String?> = Pulse(wrappedValue: "Hello tokijh")

  let oldMessagePulse: Pulse<String?> = messagePulse
  messagePulse.value = "Hello tokijh" // add valueUpdatedCount +1

  oldMessagePulse.valueUpdatedCount != messagePulse.valueUpdatedCount // true
  oldMessagePulse.value == messagePulse.value // true

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. 😊

thank you.

Looking at the // Cases, perhaps something similar to '' is correct.

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

Let's look at an additional .

The official document
distinctUntilChanged
distinctUntilChanged
the official document
example