본문 링크 (Original Link)

Learn & Master ⚔️ RxSwift 기초 10분만에 마스터하기

2018.10.08

# • # • #

by Sebastian Boldt, translated by pilgwon

titleImage

프로그래머라면 Rx를 들어보셨을 것입니다. 이런 신선한 블로그 글에서 들어보셨을 수도 있겠죠. 😎 들어본 적 없는 것은 거의 불가능이라고 생각하지만 정확히 반응형 프로그래밍(Reactive Programming)이 어떤걸까요? 일단 위키피디아를 보겠습니다.

컴퓨팅에서 반응형 프로그래밍 이란 데이터 흐름과 변경의 전달을 기반으로 하는 프로그래밍 패러다임입니다. 즉 사용된 프로그래밍 언어에서 정적 또는 동적 데이터 흐름을 쉽게 표현할 수 있어야하며 실행 모델이 변경 사항을 자동으로 전파한다는 것을 의미합니다. - Wikipedia

이 문단을 읽고 나서 완벽하게 이해한 사람은 거의 없을 것입니다. 솔직히 말하면 저도 그랬습니다. 그래서 이해하기 쉽고 간편한 RxSwift 입문서를 작성하게 되었습니다.


1. Observable Sequences 🎞

가장 처음으로 알아야 할 것은 RxSwiftObservable Sequences, 그것들을 연산(operate)하거나 Observable Sequences 에서 발생한 이벤트를 구독하는 것들로 이루어져 있습니다.

배열, 스트링 또는 딕셔너리는 RxSwift에서 Observable Sequences 로 전환됩니다. Swift 표준 라이브러리의 시퀀스 프로토콜 에 적합하다면 어떤 객체라도 Observable Sequences로 만들 수 있습니다.

이제 Observable Sequences를 만들어봅시다.

let helloSequence = Observable.just("Hello Rx")

let fibonacciSequence = Observable.from([0,1,1,2,3,5,8])

let dictSequence = Observable.from([1:"Hello",2:"World"])

**subscribe(on:(Event)-> ())** 메소드를 호출하면 Observable Sequences를 구독할 수 있습니다. 전달된 블록엔 그 시퀀스에서 발생되는 모든 이벤트를 전달받게 될 것입니다.

let helloSequence = Observable.of("Hello Rx")

let subscription = helloSequence.subscribe { event in
  print(event)
}

결과:
next("Hello Rx")
completed

Observable Sequences는 일생동안 0개 이상의 이벤트를 발생시킵니다. RxSwift 에선 이벤트는 3가지 상태의 열거형(Enumeration Type) 값을 가집니다.

let helloSequence = Observable.from(["H","e","l","l","o"])

let subscription = helloSequence.subscribe { event in
  switch event {
      case .next(let value):
          print(value)
      case .error(let error):
          print(error)
      case .completed:
          print("completed")
  }
}

결과:
H e l l o
completed

구독을 취소하고 싶다면 dispose 를 호출하면 됩니다. 또는 deinit 시에 등록된 모든 구독을 취소해주는 DisposeBag 에 구독을 등록하는 방법도 있습니다. 또 다른 방법으로는 전달받고 싶은 특정 이벤트에만 구독하는 것입니다. 예를 들어 시퀀스에서 발생하는 에러 이벤트만을 받고 싶다면 subscribe(onError:(Error->())) 를 사용하면 됩니다.

아래 코드로 지금까지 배운것들을 복습해봅시다.

// DisposeBag을 만들어서 구독이 제대로 취소되도록 합니다
let bag = DisposeBag()

// 스트링 값을 발생시키는 Observable Sequences를 만듭니다
let observable = Observable.just("Hello Rx!")

// 다음 이벤트를 위한 구독을 생성합니다
let subscription = observable.subscribe (onNext:{
    print($0)
})

// DisposeBag 에 구독을 추가합니다
subscription.addDisposableTo(bag)

2. Subjects 📫

Subject는 Observable Sequence의 특별한 형태입니다. 여러분은 구독할 수도 있고 동적으로 요소를 추가할 수도 있습니다. 지금 RxSwift 에는 4가지 서로 다른 종류의 Subject 가 존재합니다.

이 글에선 PublishSubject 가 어떻게 작동하는지만 볼 것입니다. 다른 subject 종류에 대해 더 자세히 알고 싶으시다면 GitHub 을 참고해주세요. 이들은 기본적으론 똑같고 구독할 때 받을 수 있는 과거 이벤트 수의 차이가 있습니다.

Publish: 0개 Behaviour & Variable: 1개 Replay: N개

PublishSubject 에 대해 알아보겠습니다.

가장 먼저 해야 할 것은 실제 PublishSubject 인스턴스를 만드는 것입니다. 이것은 엄청 쉬우며 기본 이니셜라이저를 사용할 수 있습니다.

let bag = DisposeBag()
var publishSubject = PublishSubject<String>()

이 시퀀스에는 onNext() 함수를 이용해서 새로운 값을 추가할 수 있습니다. onCompleted() 는 시퀀스를 완료시키고 onError(error) 는 에러 이벤트를 발생시킵니다. PublishSubject 에 값을 추가해보겠습니다.

publishSubject.onNext("Hello")
publishSubject.onNext("World")

onNext() 를 사용해서 “Hello”와 “World”를 추가한 이후에 subject를 구독하면 이벤트를 통해서 두 값을 받을 수 없습니다. 반대로 BehaviourSubject 는 가장 최근 이벤트인 “World”를 받게 됩니다.

이제 구독을 만들고 Subject에 몇가지 값을 추가해보겠습니다. 그리고 두번째 구독을 만들고 값을 몇 개 더 추가하겠습니다. 실제로 어떤 일이 일어나는지는 아래의 주석을 확인해주세요.

let subscription1 = publishSubject.subscribe(onNext:{
  print($0)
}).addDisposableTo(bag)

// subscription1은 이벤트 두 개를 받게되고, subscription2는 받지 않습니다
publishSubject.onNext("Hello")
publishSubject.onNext("Again")

// subscription2는 나중에 구독했기 때문에 "Hello"와 "Again"을 받지 못합니다
let subscription2 = publishSubject.subscribe(onNext:{
  print(#line,$0)
})

publishSubject.onNext("Both Subscriptions receive this message")

축하합니다 🎉 RxSwift 의 기본적인 내용은 다 읽으셨습니다! 더 알아야 할 내용은 많지만 Rx의 모든 것은 지금까지의 간단한 규칙들을 기반으로 합니다. 이 부분에서 지금까지 배운 내용들을 복습하거나 잠시 휴식을 취하셔도 좋습니다. 준비되셨다면 이제 더 흥미로운 내용들을 알아보러 가보겠습니다.

3. Marble Diagrams 🙌🏼

RxSwift나 Rx를 작업하고 있다면 Marble Diagrams 을 알아야 합니다. Marble Diagram 은 observable sequence의 변형을 시각화 한 도식입니다. 위쪽에는 입력 스트림이 있고 출력 스트림은 아래쪽에 있습니다. 그리고 실제 변형 함수는 중간에 있습니다.

observable sequence의 이벤트 발생을 150 밀리초로 미루는 연산을 예로 들어보겠습니다. 스케쥴러 파라미터는 무시해주세요. 이 부분은 나중에 설명드리겠습니다.

image1

어렵지 않죠?

모바일 기기에서 이러한 다이어그램을 인터랙티브하게 둘러볼 수 있는 오픈 소스 프로젝트가 iOS안드로이드 모두에게 존재합니다. 다이어그램을 갖고놀다보면 짧은 시간에 Rx 에 대해 저절로 많이 익숙해질 것입니다.

Web: http://rxmarbles.com iOS: https://itunes.apple.com/com/app/rxmarbles/id1087272442 Android: https://goo.gl/b5YD8K

4. Transformations ⚙️

구독자가 Observable Sequence에서 발생되는 이벤트를 받기 전에 변형, 조합 또는 필터링하고 싶을 땐 어떻게 해야할까요? 여기서 소개시켜드릴 것은 가장 기본적인 변형(Transformations) 연산자입니다. 마지막에는 각 변형(transformations), 조합(combinations)을 사용하는 방법에 대해 보여드리겠습니다. Let’s get started.

4.1 Map

Observable Sequence에서 발생되는 요소가 구독자들에게 닿기 전에 변형하려면 map 연산자를 사용하면 됩니다. 다음은 발생하기 전에 각 요소들의 값에 10을 곱하는 경우입니다.

image2

Observable<Int>.of(1,2,3,4).map { value in
  return value * 10
}.subscribe(onNext:{
  print($0)
})

결과: 10 20 30 40

4.2 FlatMap

Observable Sequence가 Observable로 이루어져 있고 그 안에서 새로운 Sequence를 생성하는 것을 상상해보겠습니다. 이것이 FlatMap 이 나오게 된 이유입니다. FlatMap 은 결과로 오는 Observable들을 합치고 그 결과를 Sequence로 돌려줍니다.

image2

let sequence1  = Observable<Int>.of(1,2)
let sequence2  = Observable<Int>.of(1,2)

let sequenceOfSequences = Observable.of(sequence1,sequence2)

sequenceOfSequences.flatMap{ return $0 }.subscribe(onNext:{
    print($0)
})

결과: 1 2 1 2

4.3 Scan

Scan은 초기 값으로 시작하고 Swift의 reduce처럼 값을 합칠 때 사용합니다.

image3

Observable.of(1,2,3,4,5).scan(0) { seed, value in
    return seed + value
}.subscribe(onNext:{
    print($0)
})

OUTPUT: 1 3 6 10 15

4.4 Buffer

Buffer 연산자는 요소를 발생하는 Observable을 그 요소들의 컬렉션을 발생하는 Observable로 변형시킵니다.

image4

SequenceThatEmitsWithDifferentIntervals
          .buffer(timeSpan: 150, count: 3, scheduler:s)
          .subscribe(onNext:{
    print($0)
})

결과: [1] [2,3] [4] [5,6] [7] []

5.Filter 🛡

특정 조건에 맞게 다음 이벤트를 조절하려면 filter 연산자를 사용하면 됩니다.

5.1 Filter

기본적인 filter 연산자는 Swift의 등식과 비슷하게 작동합니다. 그냥 조건문을 설정하고 넘기면 그 조건에 맞는 결과만 .next 이벤트에서 구독자들에게 보냅니다.

image5

Observable.of(2,30,22,5,60,1).filter{$0 > 10}.subscribe(onNext:{
      print($0)
})

결과: 30 22 60

5.2 DistinctUntilChanged

요소의 변화가 있을 때만 next 이벤트를 발생시키고 싶다면 distinctUntilChanged 연산자를 사용하면 됩니다.

image6

Observable.of(1,2,2,1,3).distinctUntilChanged().subscribe(onNext:{
    print($0)
})

결과: 1 2 1 3

더 알아보면 좋을 filter 연산자들:

6. Combine 💑

Sequence를 조합하는 것은 흔한 일입니다. RxSwift는 아주 많은 연산자를 제공하고 있는데 그 중 3개만 알아보겠습니다.

6.1 StartWith

Observable의 값을 발생하기 전에 특정 sequence를 앞에 추가하고 싶다면 startWith 연산자를 사용하면 됩니다.

image7

Observable.of(2,3).startWith(1).subscribe(onNext:{
    print($0)
})

결과: 1 2 3

6.2 Merge

Merge 연산자를 사용하면 여러개의 Observable의 결과를 하나의 Observable인 것처럼 조합할 수 있습니다.

image8

let publish1 = PublishSubject<Int>()
let publish2 = PublishSubject<Int>()

Observable.of(publish1,publish2).merge().subscribe(onNext:{
    print($0)
})

publish1.onNext(20)
publish1.onNext(40)
publish1.onNext(60)
publish2.onNext(1)
publish1.onNext(80)
publish2.onNext(2)
publish1.onNext(100)

결과: 20 40 60 1 80 2 100

6.3 Zip

서로 다른 Observable Sequence의 요소들을 하나의 Observable Sequence로 만들고 싶다면 Zip 메소드를 사용하면 됩니다. Zip 은 엄격한 순서로 작동해서 Zip 이 발생시키는 첫 두 요소는 각 시퀀스의 첫 번째 요소가 됩니다. Zip 의 결과 수는 원래 Observable 중 가장 적은 요소를 가진 것의 수를 따라간다는 사실을 기억하세요.

image9

let a = Observable.of(1,2,3,4,5)
let b = Observable.of("a","b","c","d")

Observable.zip(a,b){ return ($0,$1) }.subscribe {
    print($0)
}

결과: (1, "a")(2, "b") (3, "c") (4, "d")

더 알아볼 내용들:

7. Side Effects 👉

특정 이벤트가 발생했을 때 실행되는 콜백 함수를 등록하고 싶다면 doOn 연산자를 사용하면 됩니다. 이는 발생되는 요소를 수정하진 않고 그저 전달하기만 합니다. doOn 연산자엔 다음과 같은 것들이 있습니다.

Observable.of(1,2,3,4,5).do(onNext: {
    $0 * 10 // 이는 실제 구독에는 아무 영향을 끼치지 않습니다
}).subscribe(onNext:{
    print($0)
})

8.스케쥴러(Scheduler) ⏰

연산자들은 구독이 생성되는 곳과 동일한 스레드에서 작동합니다. RxSwift에선 스케쥴러를 사용해서 연산자가 특정 큐에서 작동하도록 강제할 수 있습니다. 또한 구독을 특정 큐에서 일어나도록 강제할 수도 있습니다. 이런 업무를 할 땐 subscribeOnobserveOn 를 사용합니다. 오퍼레이션 큐(operation-queue)와 디스패치 큐(dispatch-queue)의 개념에 익숙하시다면 이것도 어렵지 않을 것입니다. 스케쥴러는 GCD나 OperationQueue처럼 serial하거나 concurrent합니다. RxSwift엔 총 5가지의 스케쥴러가 있습니다.

다음은 백그라운드 큐에서 concurrent한 일을 하면서 메인 큐에서 구독하는 것의 예시입니다.

let publish1 = PublishSubject<Int>()
let publish2 = PublishSubject<Int>()

let concurrentScheduler = ConcurrentDispatchQueueScheduler(qos: .background)

Observable.of(publish1,publish2)
          .observeOn(concurrentScheduler)
          .merge()
          .subscribeOn(MainScheduler())
          .subscribe(onNext:{
    print($0)
})

publish1.onNext(20)
publish1.onNext(40)

결과: 20 40

수고하셨습니다 🎁

축하합니다. 당신은 RxSwift의 기초적인 내용을 마스터하셨습니다! 즐거운 코딩하세요 🎉

Learn & Master RxCocoa 도 기대해주세요…


참고한 내용들: