본문 링크 (Original Link)

RxSwift - 시작하기

2018.04.10

#

by RxSwift, translated by pilgwon

이 프로젝트는 ReactiveX.io와 일관성을 유지할 예정입니다. 크로스 플랫폼 문서 및 튜토리얼은 RxSwift의 경우에도 유효해야 합니다.

  1. 옵저버블 혹은 시퀀스 (Observables aka Sequences)
  2. 해제하기 (Disposing)
  3. 내장된 Observable에 대해 보증된 사항들 (Implicit Observable guarantees)
  4. Creating your first Observable (aka observable sequence)
  5. Creating an Observable that performs work
  6. Sharing subscription and shareReplay operator
  7. Operators
  8. Playgrounds
  9. Custom operators
  10. Error handling
  11. Debugging Compile Errors
  12. Debugging
  13. Debugging memory leaks
  14. Enabling Debug Mode
  15. KVO
  16. UI layer tips
  17. Making HTTP requests
  18. RxDataSources
  19. Driver
  20. Traits: Driver, Single, Maybe, Completable
  21. Examples

Observables aka Sequences

Basics

옵저버 패턴(Observable<Element> 시퀀스)과 보통의 시퀀스(Sequence) 사이의 동등성을 아는 것은 Rx를 이해하기 위한 가장 중요한 요소 중 하나입니다.

모든 Observable 시퀀스는 그냥 시퀀스이기도 합니다. Observable과 Swift의 Sequence간의 비교 중 가장 핵심적인 이점은 요소들을 비동기로 받을 수 있다는 점입니다. 이것은 RxSwift의 커널이며, 여기 문서에서는 그 아이디어를 확장하는 방법에 대해 다룰 것입니다.

시퀀스는 시각화하기 쉬운 간단하고 친근한 개념입니다.

인간은 거대한 시각 피질을 가진 생물입니다. 개념을 쉽게 시각화할 수 있다면, 그러한 이유에 대해 아는 것은 어렵지 않습니다.

모든 Rx 연산자의 이벤트 상태 기계를 시퀀스보다 높은 수준의 작업으로 시뮬레이션하는 시도는 우리에게 인지적 부하를 많이 줄 수 있습니다.

만약 우리가 Rx를 쓰지않고 모델 비동기 시스템을 쓴다면 그것은 아마 우리의 코드가 상태 기계와 일시적인 상태들로 가득차서 추상화하기보단 시뮬레이션해야 한다는 의미일 것입니다.

리스트와 시퀀스는 수학자와 프로그래머가 가장 먼저 배우는 개념 중 하나일 것입니다.

여기 숫자들의 시퀀스가 있습니다:

--1--2--3--4--5--6--| // 정상적으로 종료됩니다

문자로 이루어진 또 다른 시퀀스입니다:

--a--b--a--a--a---d---X // 정상적으로 종료하지 못하고 에러가 발생합니다

버튼을 누르는 일과 같이 어떤 시퀀스는 한정적인 반면에, 어떤 시퀀스는 무한합니다:

---tap-tap-------tap--->

이것들은 마블 다이어그램이라고 부릅니다. 더 많은 마블 다이어그램들이 rxmarbles.com에 있습니다.

만약 시퀀스 문법을 정규식으로 표현한다면 다음과 같을 것입니다:

next (error completed)?*

이는 다음과 같은 의미를 가집니다:

Rx의 시퀀스는 푸시 인터페이스로 표현되기도 합니다. (콜백이라고도 하죠)

enum Event<Element>  {
    case next(Element)      // 시퀀스의 다음 요소
    case error(Swift.Error) // 시퀀스에서 에러가 발생함
    case completed          // 시퀀스가 성공적으로 종료됨
}

class Observable<Element> {
    func subscribe(_ observer: Observer<Element>) -> Disposable
}

protocol ObserverType {
    func on(_ event: Event<Element>)
}

시퀀스가 completederror 이벤트를 내부 리소스들에 보냈을 때 그 계산된 시퀀스 요소는 해제될 것입니다.

시퀀스 요소가 만들어지는 것을 취소하고 리소스를 즉시 해제하고 싶다면, 반환된 구독에서 dispose를 호출하면 됩니다.

만약 시퀀스가 정해진 시간에 종료된다면, dispose를 호출하지 않거나 disposed(by: disposeBag)를 사용하지 않아도 자원 누출이 일어나지 않을 것입니다. 하지만, 그 자원들은 시퀀스가 요소를 만들어내는 일을 끝내거나 에러를 발생할때까지 사용될 것입니다.

만약 시퀀스가 버튼 탭과 같이 알아서 종료되지 않는다면, 할당된 자원들은 dispose가 호출되거나 disposeBag에 담겨있거나 takeUntil 연산자를 사용하는 등의 방법을 쓰지 않는 한 영원히 살아있을 것입니다.

dispose bag이나 takeUntil 연산자를 쓰는 것은 리소스가 확실히 정리되는데에 확실한 방법들입니다. 심지어 시퀀스가 한정된 시간안에 끝나는 경우에도 dispose bag이나 takeUntil 연산자를 쓰는 것을 추천합니다.

Swift.Error가 제네릭이 아닌지에 대해 궁금하다면 여기에서 그에 대한 내용을 확인하실 수 있습니다.

Disposing

관찰당하고 있는 시퀀스를 종료할 수 있는 추가적인 방법이 하나 있습니다. 우리가 시퀀스로 할 일을 끝냈고 다가올 요소들을 계산하기 위해 할당된 리소스들을 모두 해제하고 싶을 때, 구독(subscription)에서 dispose를 호출하면 됩니다.

여기 interval 연산자에 대한 예제입니다.

let scheduler = SerialDispatchQueueScheduler(qos: .default)
let subscription = Observable<Int>.interval(0.3, scheduler: scheduler)
    .subscribe { event in
        print(event)
    }

Thread.sleep(forTimeInterval: 2.0)

subscription.dispose()

위의 코드는 다음과 같은 결과를 출력할 것입니다:

0
1
2
3
4
5

여러분은 수동으로 dispose를 호출하는 것을 원하지 않을 것을 압니다. 이것은 그저 교육용 예제일 뿐입니다. dispose를 수동으로 호출하는 것은 나쁜 코드의 냄새가 납니다. DisposeBag, takeUntil 연산자, 또는 다른 매커니즘과 같이 구독을 dispose하는 더 나은 방법들이 많이 있습니다.

그래서 이 무언가를 출력하는 코드는 dispose를 호출한 이후에 종료되나요? 대답은 상황에 따라 다르다 입니다.

스케쥴러에 대한 내용은 여기서 더 확인하실 수 있습니다.

간단하게 말하자면 여러분엔겐 평행하게 일어나고 있는 두 개의 프로세스가 있다고 생각하시면 됩니다.

“이후에 무언가가 출력되나요?”라는 질문은 이 프로세스들이 다른 스케쥴러에 있다면 의미가 없는 질문이 됩니다.

확실하게 이해하기 위한 몇 가지 예제를 보여드리겠습니다. (observeOn여기에 설명돼있습니다)

아래와 같은 코드가 있다고 가정합니다:

let subscription = Observable<Int>.interval(0.3, scheduler: scheduler)
            .observeOn(MainScheduler.instance)
            .subscribe { event in
                print(event)
            }

// ....

subscription.dispose() // 메인 쓰레드에서 호출됩니다

dispose 호출이 반환되면 아무것도 출력되지 않습니다. 이것은 보장되어있는 일입니다.

또, 아래와 같은 경우가 있다고 가정합니다:

let subscription = Observable<Int>.interval(0.3, scheduler: scheduler)
            .observeOn(serialScheduler)
            .subscribe { event in
                print(event)
            }

// ...

subscription.dispose() // 같은 `serialScheduler`에서 실행합니다

dispose 호출이 반환되면 아무것도 출력되지 않습니다. 이것도 보장되어있는 일입니다.

Dispose Bags

Dispose Bag은 Rx에서 ARC와 비슷한 역할을 합니다.

DisposeBag이 해제됐을 때, 추가된 disposables들 모두에게 dispose를 호출합니다.

그 자체에는 dispose 메소드가 없어서 다른 목적으로 예외적인 호출은 불가능합니다. 즉각적인 정리가 필요하다면 새로운 가방을 만들면 됩니다.

  self.disposeBag = DisposeBag()

위의 코드는 오래된 참조들을 정리해주고 자원들이 해제되도록 합니다.

만약 예외적인 수동의 해제가 여전히 필요하다면, CompositeDisposable을 사용해보세요. CompositeDisposable은 원하는 기능을 가지고 있을것이지만 dispose가 호출됐을 때 새롭게 추가된 disposable도 즉시 해제시켜 버립니다.

Take until

dealloc시에 구독을 자동으로 해제하는 또 다른 방법으론 takeUntil 연산자를 사용하는 방법이 있습니다.

sequence
    .takeUntil(self.rx.deallocated)
    .subscribe {
        print($0)
    }

내장된 Observable에 대해 보증된 사항들

모든 시퀀스 생성자들(Observables)이 지켜야하는 추가적인 규칙들이 있습니다.

어떤 쓰레드에서 요소를 만들던지 상관없지만 그것들이 요소 하나를 만든 후에 옵저버인 observer.on(.next(nextElement)) 에 보냈다면, observer.on 메소드가 실행을 마칠때까지 다음 요소를 보낼 수 없습니다.

생성자들은 또한 .completed.error.next 이벤트가 끝나지 않았을 때 보내서 종료할 수 없습니다.

요약하자면, 아래의 예제와 같을 것입니다:

someObservable
  .subscribe { (e: Event<Element>) in
      print("이벤트 처리 시작")
      // 처리중
      print("이벤트 처리 끝")
  }

위의 코드는 아래와 같은 결과를 항상 출력할 것입니다:

이벤트 처리 시작
이벤트 처리 끝
이벤트 처리 시작
이벤트 처리 끝
이벤트 처리 시작
이벤트 처리 끝

절대 아래와 같이 출력될 수는 없습니다:

이벤트 처리 시작
이벤트 처리 시작
이벤트 처리 끝
이벤트 처리 끝

Creating your own Observable (aka observable sequence)

There is one crucial thing to understand about observables.

When an observable is created, it doesn’t perform any work simply because it has been created.

It is true that Observable can generate elements in many ways. Some of them cause side effects and some of them tap into existing running processes like tapping into mouse events, etc.

However, if you just call a method that returns an Observable, no sequence generation is performed and there are no side effects. Observable just defines how the sequence is generated and what parameters are used for element generation. Sequence generation starts when subscribe method is called.

E.g. Let’s say you have a method with similar prototype:

func searchWikipedia(searchTerm: String) -> Observable<Results> {}
let searchForMe = searchWikipedia("me")

// no requests are performed, no work is being done, no URL requests were fired

let cancel = searchForMe
  // sequence generation starts now, URL requests are fired
  .subscribe(onNext: { results in
      print(results)
  })

There are a lot of ways to create your own Observable sequence. The easiest way is probably to use the create function.

Let’s write a function that creates a sequence which returns one element upon subscription. That function is called ‘just’.

This is the actual implementation

func myJust<E>(_ element: E) -> Observable<E> {
    return Observable.create { observer in
        observer.on(.next(element))
        observer.on(.completed)
        return Disposables.create()
    }
}

myJust(0)
    .subscribe(onNext: { n in
      print(n)
    })

This will print:

0

Not bad. So what is the create function?

It’s just a convenience method that enables you to easily implement subscribe method using Swift closures. Like subscribe method it takes one argument, observer, and returns disposable.

Sequence implemented this way is actually synchronous. It will generate elements and terminate before subscribe call returns disposable representing subscription. Because of that it doesn’t really matter what disposable it returns, process of generating elements can’t be interrupted.

When generating synchronous sequences, the usual disposable to return is singleton instance of NopDisposable.

Lets now create an observable that returns elements from an array.

This is the actual implementation

func myFrom<E>(_ sequence: [E]) -> Observable<E> {
    return Observable.create { observer in
        for element in sequence {
            observer.on(.next(element))
        }

        observer.on(.completed)
        return Disposables.create()
    }
}

let stringCounter = myFrom(["first", "second"])

print("Started ----")

// first time
stringCounter
    .subscribe(onNext: { n in
        print(n)
    })

print("----")

// again
stringCounter
    .subscribe(onNext: { n in
        print(n)
    })

print("Ended ----")

This will print:

Started ----
first
second
----
first
second
Ended ----

Creating an Observable that performs work