관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] RxSwift의 share(), share(replay:scope:) 연산자, 여러곳에서 구독 시 한번만 수행되게끔 하는 방법 본문

RxSwift/RxSwift 응용

[iOS - swift] RxSwift의 share(), share(replay:scope:) 연산자, 여러곳에서 구독 시 한번만 수행되게끔 하는 방법

jake-kim 2022. 2. 5. 01:29

subscribe or bind 시 그만큼 해당 Observable이 create되는 것을 주의

  • 구독만 해도 해당 Observable의 `create`가 호출되면서 스트림이 생긴다는 것을 명심할 것
  • 테스트에 사용할 예시 API 정의
    enum API {
      private static var count = 0
      
      static func requestSomeAPI() -> Observable<String> {
        Single<String>.create { single in
          let params: [String: Any] = [
            "lon": 113,
            "lat": 23.1,
            "ac": 0,
            "unit": "metric",
            "output": "json",
            "tzshift": 0
          ]
          AF
            .request(
              "https://www.7timer.info/bin/astro.php",
              method: .get,
              parameters: params
            )
            .responseString { response in
              Self.count += 1
              print("request 카운트 \(Self.count)")
              switch response.result {
              case let .success(string):
                single(.success(string))
              case let .failure(error):
                single(.failure(error))
              }
            }
          
          return Disposables.create()
        }
        .asObservable()
      }
    }
    
    // https://www.7timer.info/bin/astro.php?lon=113.2&lat=23.1&ac=0&unit=metric&output=json&tzshift=0​
  • 해당 Observable을 얻어서 이 하나의 Observable을 구독하는 곳이 여러곳이라면 여러곳에서 호출되는것을 주의
    let apiObservable = API.requestSomeAPI()
    
    apiObservable
      .bind { _ in print("구독1!") }
      .disposed(by: self.disposeBag)
    
    apiObservable
      .bind { _ in print("구독2!") }
      .disposed(by: self.disposeBag)
    
    apiObservable
      .bind { _ in print("구독3!") }
      .disposed(by: self.disposeBag)
      
    /*
    request 카운트 1
    구독1!
    request 카운트 2
    구독2!
    request 카운트 3
    구독3!
    */​

구독 시 중복 호출 방지 - 별도의 PublishSubject를 두는 방벙

  • PublishSubject 개념은 이전 포스팅 글 참고
  • publishSubject를 선언해 놓고, API로 나온 Observable을 구독한 다음 값을 publishSubeject에 전달하는 방법
    class ViewController: UIViewController {
      private let disposeBag = DisposeBag()
      private let myPublichSubject = PublishSubject<String>() // <-
      
      override func viewDidLoad() {
        super.viewDidLoad()
        API.requestSomeAPI()
          .bind { [weak self] in self?.myPublichSubject.onNext($0) }
          .disposed(by: self.disposeBag)
          
    	...​
     
  • api request는 단 한번만 수행
    API.requestSomeAPI()
      .bind { [weak self] in self?.myPublichSubject.onNext($0) }
      .disposed(by: self.disposeBag)
    
    myPublichSubject
      .bind { _ in print("구독1!") }
      .disposed(by: self.disposeBag)
    
    myPublichSubject
      .bind { _ in print("구독2!") }
      .disposed(by: self.disposeBag)
    
    myPublichSubject
      .bind { _ in print("구독3!") }
      .disposed(by: self.disposeBag)
      
    /*
    request 카운트 1
    구독1!
    구독2!
    구독3!
    */

구독 시 중복 호출 방지 - share() 연산자 사용 방법

https://medium.com/gett-engineering/rxswift-share-ing-is-caring-341557714a2d

  • share() 연산자 사용 방법
    • Observable에 share()연산자를 사용하여 구독이 여러번 되어도 create가 호출되지 않는 연산자
    • 보통 Observable을 리턴하는 쪽에서 사용하므로 아래처럼보단 requestSomeAPI의 리턴 부분에 share()사용하여 사용
      let requestObservable = API.requestSomeAPI().share()
      requestObservable
        .bind { _ in print("구독1!") }
        .disposed(by: self.disposeBag)
      
      requestObservable
        .bind { _ in print("구독2!") }
        .disposed(by: self.disposeBag)
      
      requestObservable
        .bind { _ in print("구독3!") }
        .disposed(by: self.disposeBag)
      
      /*
       request 카운트 1
       구독1!
       구독2!
       구독3!
       */​

share(replay:scope:) 연산자

https://medium.com/gett-engineering/rxswift-share-ing-is-caring-341557714a2d

  • replay
    • 신규 subscriber에게 이전 방출했던 요소를 몇 개를 기록했다가 방출할(replay) 것인가
  • scope
    • .whileConnected(subscriber가 한 개 이상 있으면 replay가 유지되지만, dispose되어 0개가 되면 replay 버퍼 초기화)
    • .forever (subscriber가 없어도 버퍼 유지)

ex) replay 이해하기 - share(replay: 2, scope: .whileConnect) 예시

var count = 0
let tapObservable = self.didTapButtonObservable
  .map { _ -> Int in
    count += 1
    return count
  }
  .share(replay: 2, scope: .whileConnected)
    
tapObservable
  .do(onNext: { print($0) })
  .subscribe()
  .disposed(by: self.disposeBag)
    
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
  print("두 번째 Subscriber 구독 시작!")
  tapObservable
    .do(onNext: { print($0) })
    .subscribe()
    .disposed(by: self.disposeBag)
}

-> 두 번째 subscription에게 스트림에서 방출되었던 요소 중 2가지(replay:2)를 한꺼번에 방출

ex) whileConnect, forever 이해하기 - share(replay: 2, scope: .whileConnect) 예시

- 두 번째 subscription이 생기기 전에 firstSubscription.dispose()가 호출되어, replay 버퍼가 해지

- 즉, 이전에 방출된 값들을 두 번째 subscription에서는 받아볼 수 없음

var count = 0
let tapObservable = self.didTapButtonObservable
  .map { _ -> Int in
    count += 1
    return count
  }
  .share(replay: 2, scope: .whileConnected)

let firstSubscription = tapObservable
  .do(onNext: { print($0) })
  .subscribe()
    
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
  firstSubscription.dispose() // <-
  print("두 번째 Subscriber 구독 시작!")
  tapObservable
    .do(onNext: { print($0) })
    .subscribe()
    .disposed(by: self.disposeBag)
}

whileConnected이므로 subscriber가 dispose되면 버퍼 해제

-> .whileConnected를 .forever로 바꾸면 기존 subscriber가 dispose되어도 버퍼 유지

var count = 0
let tapObservable = self.didTapButtonObservable
  .map { _ -> Int in
    count += 1
    return count
  }
  .share(replay: 2, scope: .forever)

let firstSubscription = tapObservable
  .do(onNext: { print($0) })
  .subscribe()
    
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
  firstSubscription.dispose() // <-
  print("두 번째 Subscriber 구독 시작!")
  tapObservable
    .do(onNext: { print($0) })
    .subscribe()
    .disposed(by: self.disposeBag)
}

.forever 이므로 버퍼 초기화가 안되고 살아있는 것을 확인

cf) share() 연산자는 `share(replay:0, scope: .whileConnected)`와 동일

* 전체 코드: https://github.com/JK0369/ExShare

* 참고

https://medium.com/@_achou/rxswift-share-vs-replay-vs-sharereplay-bea99ac42168

https://medium.com/gett-engineering/rxswift-share-ing-is-caring-341557714a2d

Comments