Skip to content

Latest commit

 

History

History
189 lines (132 loc) · 8.04 KB

File metadata and controls

189 lines (132 loc) · 8.04 KB

이글은 Understanding Publish, Connect, RefCount and Share in RxSwift 에 대한 번역입니다. (번역 허락을 받은 것은 아니지만, 도움이 필요하신 분들을 위해서 올려봅니다. 문제가 될 경우 바로 삭제하겠습니다. ㅡㅜ)

RxSwift에서 Publish, Connect, RefCount 그리고 Share 이해하기

이 글에서는 RxSwift 의 Publish, Connect, RefCount 그리고 Share 연산자들에 대해서 설명합니다.

이 연산자들은 함께 다양한 조합으로 사용됩니다. 다음의 둘의 차이를 이해하는 것이 중요합니다.

  • publish().connect()
  • publish().refcount()/share()

능동(Active) 과 수동(Passive) Observable

문제의 핵심에 접근하기 전에 hot 과 cold observable 의 정의에 대해서 이야기 하고 싶습니다. 저에게 hot 과 cold observable 들은 헷갈리고 뭔가 신비로운 용어들입니다. (더 복잡하게 보이게 만듭니다... warm 과 cool observable 들에 대해서 들어 보신적 있으신가요?)

"hot observable" 을 "active sequence" 로, "cold" 를 "passive sequence" 로 이름을 바꾸도록 합시다.

  • 능동(active) sequence 들은 구독(subscription)이 있든 없든 항상 이벤트를 발생시킵니다.
  • 수동(passive) sequence 들은 요청이 있을때(구독될 때/요구될 때) 이벤트를 발생시킵니다.

구독(subscription)할 때 network 요청이 발생하는 경우를 수동(passive) sequence 의 예로 들 수 있습니다. 반면에, web socket, timer 이벤트, UITextField 로 부터 발생하는 text 값들은 능동(active) sequence 입니다.

그리고 그렇죠. hot/cold/warm/cool observable 들은 헷갈리니 능동(active) 와 수동(passive) sequence 들에 대해서만 생각하세요.

같은 Observable 에 여러번 구독하기(Subscribing)

만약 여러분이 같은 Observable 에 두번 이상 구독했다면(subscribe), 결과를 보고 놀랐을지도 모르겠습니다.

network 요청(request) 에 대한 구현을 보세요.

let URL = NSURL(string: "http://localhost:4567/example.json")!
let requestObservable = NSURLSession.sharedSession()
  .rx_data(NSURLRequest(URL: URL))

requestObservable.subscribeNext {
      print($0)
    }
requestObservable.subscribeNext {
      print($0)
    }

console 로그를 보면, 두개의 다른 HTTP 응답(response)들을 볼 수 있을 겁니다. Observable 이 여려분 의도와는 다르게 두번 동작했습니다.

share() 가 해결책

확실히 두번 동작하는 것은 우리가 원하는 것이 아닙니다. 특히, network 요청(request)는 더욱 더 아니죠. 간단히 share() 연산자를 추가해서 network 요청(request)가 한번만 호출되도록 변경합니다.

let URL = NSURL(string: "http://localhost:4567/example.json")!
let requestObservable = NSURLSession.sharedSession()
  .rx_data(NSURLRequest(URL: URL))
  .share()

requestObservable.subscribeNext {
      print($0)
    }
requestObservable.subscribeNext {
      print($0)
    }

기대한 대로, HTTP 응답(response)를 한번만 수신했습니다.

share() 연산자는 기본적으로 .publish().refcount 를 감싼 것(wrapper)입니다.

잠시만요! publish() 와 refcount() 는 그럼 뭐죠? 이 질문에 대해 아래에서 답해 봅시다.

publish() 와 그의 친구 connect()

publish() 가 사용될 때, publish() 는 보통의 observable 을 "connectable observable" 로 변환시킵니다. 공식 정의는 다음과 같습니다.

"connectable observable 은 구독될(subscribed) 때 이벤트들을 발생시키기를 시작하지 않는 것만 제외 하고는 보통의 observable 과 같습니다. Connect 연산자가 적용될 때 이벤트를 발생시키기 시작합니다. " (즉, 보통의 observable 은 subscribe 되면 바로 이벤트를 발생시키기 시작하는데, connectable observable 은 connect 연산자가 호출될 때 이벤트를 발생시키기 시작합니다.)

print("Creating observable")
let myObservable = Observable.just(1).publish()

print("Subscribing")
myObservable.subscribeNext {
  print("first = \($0)")
}

myObservable.subscribeNext {
  print("second = \($0)")
}

delay(3) {
  print("Calling connect after 3 seconds")
  myObservable.connect()
}



// Output
Creating observable
Subscribing
Calling connect after 3 seconds
first = 1
second = 1

위의 예제에서, observable 이 만들어 질 때 구독(subscription)이 같이 만들어 짐에도 불구하고 이벤트들이 발생하지 않습니다. 하지만, 3초 후에, 두개의 구독(subscription)로 부터 값들을 볼 수 있습니다.

즉, connect() 가 connectable observable 를 활성화시키고, 구독자(subscriber)들에 이벤트를 흘려보내줍니다.

재미있는 점은 구독(subscription)의 폐기(dispose)가 어떻게 처리되는가입니다. 이 코드를 한번 보세요.

print("Starting at 0 seconds")
let myObservable = Observable<Int>.interval(1, scheduler: MainScheduler.instance).publish()
myObservable.connect()

let mySubscription = observable.subscribeNext {
  print("Next: \($0)")
}

delay(3) {
  print("Disposing at 3 seconds")
  mySubscription.dispose()
}

delay(6) {
  print("Subscribing again at 6 seconds")
  myObservable.subscribeNext {
    print("Next: \($0)")
  }
}

출력:

비록 모든 구독(subscription)들이 폐기되(disposed)었어도, observable 은 여전히 살아서 아래에서 이벤트르르 발생시킵니다. 능동(active) sequece 처럼 동작합니다. 이제 .publish().refcount() 와 비교해 봅시다.

.publish().connect() 과 .publish().refcount() 의 차이점

refcount() 를 구독(subscription) 폐기(dispose)를 처리하는 마법 시스텝 처럼 생각할 수도 있습니다. refcount() 는 첫번째 observer가 구독을(subscribe) 하면 connect() 자동으로 호출합니다. 그래서 수동으로 connect() 를 호출할 필요가 없습니다.

print("Starting at 0 seconds")
    let myObservable = Observable<Int>.interval(1, scheduler: MainScheduler.instance).publish().refCount()

    let mySubscription = observable.subscribeNext {
      print("Next: \($0)")
    }

    delay(3) {
      print("Disposing at 3 seconds")
      mySubscription.dispose()
    }

    delay(6) {
      print("Subscribing again at 6 seconds")
      myObservable.subscribeNext {
        print("Next: \($0)")
      }
    }

출력:

한가지 알아 두세요: 다시 구독할(subscribe) 때, observable 은 이벤트를 새로(brand new) 발생시킵니다.

결론

이제 차이점을 아시겠나요? .publish().connect()publish().refcount() (혹은 짧게 share() ) 은 observable 들의 폐기(dispose)를 다르게 관리합니다.

publish().connect() 를 사용할 때는 observable 를 수동으로 폐기(dispose)해야 합니다. 그이외에, 능동(active) sequence 처럼 동작하고 항상 이벤트를 발생시킵니다.

반면에, publish().refcount()/share() 는 다른 observer들이 observable 에 몇 개나 구독하고(subscribe) 있는지 확인하고 마지막 observer 가 완료할 때까지 observer 로 부터 disconnect 하지 않습니다. 다른 말로, 구독(subscription) counter 가 0 으로 떨어질 때, observable은 폐기되고 더이상 이벤트를 발생시키지 않습니다.

connect()refcount() 를 확인해 보는 것도 좋습니다.

만약 명확하지 않는 것이 있다면, 댓글로 알려 주세요!

이 글을 교정해주시고 개선 사항을 제안해주신 finneycanhelpthesunshinejr 에게 감사드립니다.

감사합니다.