Learn & Master ⚔️ RxSwift 기초 10분만에 마스터하기
2018.10.08
#RxSwift • #Swift • #Learn&Master
by Sebastian Boldt, translated by pilgwon
프로그래머라면 Rx를 들어보셨을 것입니다. 이런 신선한 블로그 글에서 들어보셨을 수도 있겠죠. 😎 들어본 적 없는 것은 거의 불가능이라고 생각하지만 정확히 반응형 프로그래밍(Reactive Programming)이 어떤걸까요? 일단 위키피디아를 보겠습니다.
컴퓨팅에서 반응형 프로그래밍 이란 데이터 흐름과 변경의 전달을 기반으로 하는 프로그래밍 패러다임입니다. 즉 사용된 프로그래밍 언어에서 정적 또는 동적 데이터 흐름을 쉽게 표현할 수 있어야하며 실행 모델이 변경 사항을 자동으로 전파한다는 것을 의미합니다. - Wikipedia
이 문단을 읽고 나서 완벽하게 이해한 사람은 거의 없을 것입니다. 솔직히 말하면 저도 그랬습니다. 그래서 이해하기 쉽고 간편한 RxSwift 입문서를 작성하게 되었습니다.
1. Observable Sequences 🎞
가장 처음으로 알아야 할 것은 RxSwift 는 Observable 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
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) 값을 가집니다.
- .next(value: T) : Observable Sequences에 하나 이상의 값이 추가되면 위의 예시처럼 다음 이벤트 를 구독자들에게 전달합니다. 전달되는 값 중에는 시퀀스의 실제 값이 포함됩니다.
- .error(error: Error) : 에러를 만나면 시퀀스는 에러 이벤트 를 발생시킵니다. 이는 시퀀스를 종료시킵니다.
- .completed : 시퀀스가 정상적으로 끝나면 완료 이벤트 를 구독자들에게 전달합니다.
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 : 구독 이후에 발생하는 모든 이벤트를 전달받습니다.
- BehaviourSubject : 구독 전에 발생한 최근 요소와 이후에 발생하는 모든 것들을 전달받습니다.
- ReplaySubject : 가장 최근이 아닌 모든 구독 이전에 발생한 내용을 받고싶다면 ReplaySubject 를 사용하면 됩니다. ReplaySubject 는 새로운 구독자에게 몇 개의 최근 요소를 전달할 지 정할 수 있습니다.
- Variable : Variable은 BehaviourSubject 를 비 반응형 프로그래밍에도 자연스럽게 보여지도록 만드는 껍데기(Wrapper)입니다. 보통의 변수처럼 사용할 수 있습니다.
이 글에선 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 밀리초로 미루는 연산을 예로 들어보겠습니다. 스케쥴러 파라미터는 무시해주세요. 이 부분은 나중에 설명드리겠습니다.
어렵지 않죠?
모바일 기기에서 이러한 다이어그램을 인터랙티브하게 둘러볼 수 있는 오픈 소스 프로젝트가 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을 곱하는 경우입니다.
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로 돌려줍니다.
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처럼 값을 합칠 때 사용합니다.
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로 변형시킵니다.
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 이벤트에서 구독자들에게 보냅니다.
Observable.of(2,30,22,5,60,1).filter{$0 > 10}.subscribe(onNext:{
print($0)
})
결과: 30 22 60
5.2 DistinctUntilChanged
요소의 변화가 있을 때만 next 이벤트를 발생시키고 싶다면 distinctUntilChanged 연산자를 사용하면 됩니다.
Observable.of(1,2,2,1,3).distinctUntilChanged().subscribe(onNext:{
print($0)
})
결과: 1 2 1 3
더 알아보면 좋을 filter 연산자들:
- Debounce
- TakeDuration
- Skip
6. Combine 💑
Sequence를 조합하는 것은 흔한 일입니다. RxSwift는 아주 많은 연산자를 제공하고 있는데 그 중 3개만 알아보겠습니다.
6.1 StartWith
Observable의 값을 발생하기 전에 특정 sequence를 앞에 추가하고 싶다면 startWith 연산자를 사용하면 됩니다.
Observable.of(2,3).startWith(1).subscribe(onNext:{
print($0)
})
결과: 1 2 3
6.2 Merge
Merge 연산자를 사용하면 여러개의 Observable의 결과를 하나의 Observable인 것처럼 조합할 수 있습니다.
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 중 가장 적은 요소를 가진 것의 수를 따라간다는 사실을 기억하세요.
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")
더 알아볼 내용들:
- Concat
- CombineLatest
- SwitchLatests
7. Side Effects 👉
특정 이벤트가 발생했을 때 실행되는 콜백 함수를 등록하고 싶다면 doOn 연산자를 사용하면 됩니다. 이는 발생되는 요소를 수정하진 않고 그저 전달하기만 합니다. doOn 연산자엔 다음과 같은 것들이 있습니다.
- do(onNext:) - next 이벤트가 발생했을 때 무언가를 하고싶다면 사용합니다
- do(onError:) - 에러 이벤트가 발생했을 때 사용합니다
- do(onCompleted:) - sequence가 성공적으로 끝날 때 사용합니다
Observable.of(1,2,3,4,5).do(onNext: {
$0 * 10 // 이는 실제 구독에는 아무 영향을 끼치지 않습니다
}).subscribe(onNext:{
print($0)
})
8.스케쥴러(Scheduler) ⏰
연산자들은 구독이 생성되는 곳과 동일한 스레드에서 작동합니다. RxSwift에선 스케쥴러를 사용해서 연산자가 특정 큐에서 작동하도록 강제할 수 있습니다. 또한 구독을 특정 큐에서 일어나도록 강제할 수도 있습니다. 이런 업무를 할 땐 subscribeOn 와 observeOn 를 사용합니다. 오퍼레이션 큐(operation-queue)와 디스패치 큐(dispatch-queue)의 개념에 익숙하시다면 이것도 어렵지 않을 것입니다. 스케쥴러는 GCD나 OperationQueue처럼 serial하거나 concurrent합니다. RxSwift엔 총 5가지의 스케쥴러가 있습니다.
-
MainScheduler — 추상적인 업무는
MainThread
에서 실행돼야 합니다.schedule
메소드가 메인 스레드에서 호출됐을 경우 이는 스케쥴링 없이 즉시 액션을 실행합니다. 이 스케쥴러는 보통 UI 작업에서 쓰입니다. -
CurrentThreadScheduler - 현재 스레드의 작업 유닛을 스케쥴합니다. 요소를 생성하는 연산자들의 기본적인 스케쥴러입니다.
-
SerialDispatchQueueScheduler — 특정
dispatch_queue_t
에서 실행되는 작업을 추상화합니다. dispatch queue가 concurrent하더라도 serial하게 변경합니다. Serial 스케쥴러는observeOn
에 대한 최적화를 가능하게 해줍니다. 메인 스케쥴러는SerialDispatchQueueScheduler
의 인스턴스입니다. -
ConcurrentDispatchQueueScheduler — 특정
dispatch_queue_t
에서 실행되는 작업을 추상화합니다. serial dispatch queue를 전달할 수 있고 아무 문제도 일으키지 않습니다. 이 스케쥴러는 백그라운드에서 실행될 작업에 적합합니다. -
OperationQueueScheduler — 특정
NSOperationQueue
에서 실행되는 작업을 추상화합니다. 이 스케쥴러는 큰 덩어리의 업무가 백그라운드에서 실행돼야하고maxConcurrentOperationCount
를 사용해서 concurrent한 처리 과정을 미세 조정하고 싶을 때 사용합니다.
다음은 백그라운드 큐에서 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 도 기대해주세요…
참고한 내용들: