iOS

Swift ReactorKit @Pulse 사용 목적, 예시, 일반 State와의 차이

crazydeer 2024. 6. 1. 21:45
반응형

https://github.com/ReactorKit/ReactorKit/tree/master/Examples/Counter/Counter

 

ReactorKit/Examples/Counter/Counter at master · ReactorKit/ReactorKit

A library for reactive and unidirectional Swift applications - ReactorKit/ReactorKit

github.com

Counter 예제와 같이 봅니다.

 

 

ReactorKit에서 @Pulse는 특정 상태 변화를 옵저빙(감지)하기 위해 사용되는 프로퍼티 래퍼입니다. @Pulse를 사용하면 특정 상태 값이 변경될 때만 옵저버가 트리거됩니다. 이는 주로 일회성 이벤트나 특정 상태 변화에 반응하는 경우에 유용합니다.

 

 

@Pulse 변수

@Pulse의 사용 목적

  • 일회성 이벤트 처리: 일반적인 상태 변화와는 다르게, 사용자에게 한 번만 표시되어야 하는 알림 메시지나 토스트 메시지 같은 이벤트에 적합합니다. 이러한 이벤트는 상태 변화가 있을 때마다 발생하는 것이 아니라, 한 번 발생하고 나면 상태를 초기화하거나 더 이상 감지되지 않도록 해야 합니다.
  • 필요한 상태 변화만 반응: @Pulse를 사용하면 해당 상태 값이 변경될 때만 반응합니다. 이는 불필요한 상태 변화 감지를 줄이고 성능을 최적화할 수 있습니다.

코드 예제

struct State {
    var value: Int
    var isLoading: Bool
    @Pulse var alertMessage: String?
}

일반 State와의 차이

일반 상태 변수를 사용하면 상태가 변경될 때마다 모든 구독자가 반응합니다. 예를 들어, alertMessage가 일반 상태 변수라면, 매번 상태가 업데이트될 때마다 UI가 업데이트를 시도할 수 있습니다. 이는 다음과 같은 문제가 발생할 수 있습니다:

  • 불필요한 업데이트: 상태가 자주 변경되는 경우, 불필요하게 UI가 업데이트될 수 있습니다.
  • 일회성 이벤트 처리의 어려움: alertMessage가 매번 상태가 변경될 때마다 트리거된다면, 한 번 표시된 알림 메시지가 다시 표시될 수 있습니다.

예제 코드 분석

reactor.pulse(\.$alertMessage)
  .compactMap { $0 }
  .subscribe(onNext: { [weak self] message in
    let alertController = UIAlertController(
      title: nil,
      message: message,
      preferredStyle: .alert
    )
    alertController.addAction(UIAlertAction(
      title: "OK",
      style: .default,
      handler: nil
    ))
    self?.present(alertController, animated: true)
  })
  .disposed(by: disposeBag)
  • reactor.pulse(\\\\.$alertMessage): @Pulse로 정의된 alertMessage가 변경될 때만 트리거됩니다.
  • .compactMap { $0 }: alertMessage가 nil이 아닌 경우에만 처리합니다.
  • subscribe(onNext:): 알림 메시지를 UI에 표시합니다.

결론

@Pulse를 사용하는 이유는 특정 상태 변화에만 반응하기 위해서입니다. 특히 일회성 이벤트나 특정 조건에만 반응해야 하는 경우에 유용합니다. 일반 상태 변수로 대체할 수 있지만, 그 경우 불필요한 업데이트가 발생할 수 있고, 일회성 이벤트 처리가 어려울 수 있습니다. @Pulse는 이러한 문제를 해결해 주기 때문에 사용이 권장됩니다.

Pulse 사용 예시

@Pulse는 주로 일회성 이벤트나 특정 조건이 충족될 때만 반응해야 하는 상태를 관리하는 데 유용합니다. 아래에 @Pulse를 사용하기 좋은 몇 가지 예시를 소개합니다:

1. 네비게이션 이벤트

앱에서 특정 액션이 발생할 때 다른 화면으로 이동해야 하는 경우에 사용됩니다.

struct State {
    @Pulse var navigateToDetail: Bool?
}

// Reactor
if someCondition {
    return .just(.setNavigateToDetail(true))
}

// ViewController
reactor.pulse(\.$navigateToDetail)
    .compactMap { $0 }
    .filter { $0 == true }
    .subscribe(onNext: { [weak self] _ in
        let detailViewController = DetailViewController()
        self?.navigationController?.pushViewController(detailViewController, animated: true)
    })
    .disposed(by: disposeBag)

2. 애니메이션 트리거

애니메이션이 특정 상태 변화에 의해 한 번만 실행되어야 하는 경우에 사용됩니다.

struct State {
    @Pulse var triggerAnimation: Bool?
}

// Reactor
if someCondition {
    return .just(.setTriggerAnimation(true))
}

// ViewController
reactor.pulse(\.$triggerAnimation)
    .compactMap { $0 }
    .filter { $0 == true }
    .subscribe(onNext: { [weak self] _ in
        self?.runAnimation()
    })
    .disposed(by: disposeBag)

3. 폼 검증 결과

폼을 제출할 때 검증 결과를 표시하는 메시지에도 사용될 수 있습니다.

struct State {
    @Pulse var formValidationResult: String?
}

// Reactor
if isValidForm {
    return .just(.setFormValidationResult("Form is valid"))
} else {
    return .just(.setFormValidationResult("Form is invalid"))
}

// ViewController
reactor.pulse(\.$formValidationResult)
    .compactMap { $0 }
    .subscribe(onNext: { [weak self] message in
        self?.showValidationResult(message)
    })
    .disposed(by: disposeBag)

4. 로딩 인디케이터 표시

네트워크 요청이나 긴 작업이 완료되었을 때 로딩 인디케이터를 숨기는 데 사용될 수 있습니다.

struct State {
    @Pulse var showLoading: Bool?
}

// Reactor
func mutate(action: Action) -> Observable<Mutation> {
    switch action {
    case .startLoading:
        return Observable.concat([
            Observable.just(Mutation.setShowLoading(true)),
            someLongRunningTask().map { _ in Mutation.setShowLoading(false) }
        ])
    }
}

// ViewController
reactor.pulse(\.$showLoading)
    .compactMap { $0 }
    .subscribe(onNext: { [weak self] show in
        self?.loadingIndicator.isHidden = !show
    })
    .disposed(by: disposeBag)

5. 권한 요청

앱에서 특정 권한을 요청하는 경우, 권한 요청이 완료된 후 사용자에게 알림을 표시하는 데 사용할 수 있습니다.

struct State {
    @Pulse var permissionRequestResult: Bool?
}

// Reactor
func mutate(action: Action) -> Observable<Mutation> {
    switch action {
    case .requestPermission:
        return requestPermission().map { granted in
            return Mutation.setPermissionRequestResult(granted)
        }
    }
}

// ViewController
reactor.pulse(\.$permissionRequestResult)
    .compactMap { $0 }
    .subscribe(onNext: { [weak self] granted in
        if granted {
            self?.showPermissionGrantedMessage()
        } else {
            self?.showPermissionDeniedMessage()
        }
    })
    .disposed(by: disposeBag)

이러한 다양한 사례에서 @Pulse를 사용하면, 상태 변화가 특정 조건에만 반응하도록 만들 수 있어 불필요한 UI 업데이트를 방지하고, 보다 명확한 상태 관리가 가능합니다.

반응형