Medium - Learn & Master ⚔️ the Basics of RxSwift in 10 Minutes
in Trend
Trend 파악을 Medium 기고문 요약 포스팅 - 10분안에 RxSwift를 배워봅시다!
모든 개발자들은 Rx에 대해 들어봤을겁니다. 마지막 개발자 컨퍼런스에서 혹은 이것처럼 신선한 블로그 기사를 통해서 들어봤겠죠😎. Rx에 대해 들어보지 못했다는 것은 거의 불가능합니다 다만, Reactive Programming이란 것은 정확히 뭘까요? 인터넷의 정의를 살펴봅시다:
컴퓨터분야에서 reactive programming은 데이터 흐름과 변화의 전달을 기반으로 하는 프로그래밍 패러다임을 말합니다. 이 말은 리액티브 프로그래밍은 쉽게 데이터의 흐름을 동적이나 정적으로 전달할 수 있음을 말하고 데이터 흐름을 통해 변화가 전달되면 자동적으로 내재된 실행 모델이 수행됨을 말합니다. - 위키피디아
솔직히 말해서 이 문단을 읽고나서 대부분의 사람들은 리액티브 프로그래밍이 무엇인지 감을 못 잡을 겁니다. 저도 그랬어요. 때문에 제가 RxSwift를 이용하여 현대적인 소프트웨어 접근법인 Rx에 대해 간단하고 이해하기 쉬운 입문서를 만들게 되었습니다.
1. Observable Sequences 🎞
처음으로 알아야 할 점은 RxSwift에 있는 모든 것은 관찰가능한 흐름(Observable sequence)이거나 이벤트로부터 방출된 관찰가능한 흐름에 operate를 수행하거나 구독하는 것입니다.
RxSwift에서 배열이나 문자열, 딕셔너리 등은 observable sequences로 변환됩니다. 당신은 Swift의 표준라이브러리를 사용해서 어떤 객체라도 Sequence Protocol을 준수하도록 하여 observable sequence로 만들 수 있습니다.
옵저버블 시퀀스를 만들어봅시다
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)
}
OUTPUT:
next("Hello Rx")
completed
옵저버블 시퀀스는 0개 이상의 이벤트를 뱉어낼 수 있습니다. RxSwift에서 이벤트는 다음과 같은 3 가지의 열거형 타입입니다.
- .next(value: T) - 콜렉션 값이나 단일 값이 옵저버블 시퀀스에 추가되는 경우 서브스크라이버에 next event를 보냅니다. 연관된 값은 시퀀스의 실제 값입니다.
- .error(error: Error) - 에러가 발생했을 경우 시퀀스는 error event를 발생합니다. 그리고 시퀀스는 종료됩니다.
- .completed - 시퀀스가 정상적으로 끝난 경우 completed event를 서브스크라이버에게 보냅니다.
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") } } OUTPUT: H e l l o completed
만약 구독을 취소하고 싶으면 dispose를 호출함으로써 가능합니다. subscription에 Disposebag를 추가해서 Disposebag인스턴스가 소멸될 때 자동적으로 구독을 취소할 수도 있습니다. 또 다른 점은 특정 이벤트만 구독할 수도 있습니다. 예를 들어 시퀀스가 에러 이벤트만 뱉어내는 걸 받고 싶으면 subscribe(onError:(Error_>())).를 사용하면 됩니다.
아래의 코드는 지금까지 설명한 내용을 통합한 것 입니다.
// DisposeBag을 만들면 구독을 정확하게 취소하게 될겁니다.
let bag = DisposeBag()
// 문자열 값을 뱉는 옵저버블 시퀀스를 만듭시다.
let observable = Observable.just("Hello Rx!")
// next event에만 구독을 하도록 만듭시다.
let subscription = observable.subscribe (onNext:{
print($0)
})
// 서브스크립션에 disposeBag을 추가합시다.
subscription.addDisposableTo(bag)
2. Subjects📫
Subject는 옵저버블 시퀀스의 특별한 형태입니다. 당신은 구독도 할 수 있지만 동적으로 요소를 추가할 수 있습니다. 현재 RxSwift에는 4 종류의 Subjects들이 있습니다.
- PublishSubject - 구독을 하게되면 구독 이후의 모든 이벤트를 받게 됩니다.
- BehaviorSubject - 구독자에게 가장 최근의 요소와 구독 이후 뱉어지는 것을 넘겨줍니다.
- ReplaySubject - 새로 만들어진 구독자에게 하나 이상의 최근 요소들을 넘겨주고 싶다면 ReplaySubject를 사용하면 됩니다. ReplaySubject는 새로운 구독자에게 최근 몇 개의 아이템을 전달할 지 정의할 수 있습니다.
- Variable - Variable은 BehaviorSubject의 wrapper입니다. 일반 변수처럼 사용할 수 있으며 좀 더 비 리액티브프로그래밍처럼 느껴지게 됩니다.
이 기사에서는 오적 PublishSubject가 어떻게 동작 하는지만 설명할 것입니다. 다른 Subject 타입에 대해서 더 알고싶다면 GitHub를 참고하세요. Subjects들은 기본적으로 초기화 될 때 구독자에게 몇 개의 이벤트를 넘겨주느냐 하는 것이 다릅니다.
publish : 0, Behavior & Variable : 1, Replay : N
PublishSubject를 살펴봅시다.
처음으로 해야할 일은 실제적인 publishSubject 인스턴스를 생성하는 것 입니다. 이것은 매우 쉽죠, 우리는 기본적인 생성자를 사용할 수 있습니다.
let bag = DisposeBag()
var publishSubject = PublishSubject<String>()
onNext()를 사용해서 시퀀스에 새로운 값을 더할 수 있습니다. onComplete()는 시퀀스를 끝낼 때 호출될 것이고 onError(error)는 에러 이벤트가 발생할 때 호출될 것입니다. publishSubject에 값 들을 추가해 봅시다.
publishSubject.onNext("Hello")
publishSubject.onNext("World")
만약 “Hello”와 “World”를 onNext를 사용해서 추가한 뒤에 Subject를 구독한다면 이벤트를 통해 두 값을 받을 수 없습니다. 반대로 BehaviorSubject는 최근 이벤트인 “World”를 전달해 줍니다.
자 그럼 이제 Subscription을 생성하고 Subject에 값들을 추가해 줍시다. 그리고 두 번째 Subscription을 추가하고 값들을 더욱 추가해 줍시다. 실제로 무슨 일이 일어나는 이해하기 위해 코멘트를 꼭 읽어주세요.
let subscription1 = publishSubject.subscribe(onNext:{
print($0)
}).addDisposableTo(bag)
subscription1은 2개의 이벤트를 받을 것이고 subscription2는 받지 못합니다.
publishSubject.onNext("Hello")
publishSubject.onNext("Again")
// Sub2는 "Hello"와 "Again"을 받지 못합니다. 왜냐면 나중에 구독했기 때문이죠
let subscription2 = publishSubject.subscribe(onNext:{
print(#line,$0)
})
publishSubject.onNext("Both Subscriptions receive this message")
축하합니다 🎉. 만약 여기까지 읽으셨다면 당신은 RxSwift의 기본을 알게되셨을 겁니다. 이 것보다 더욱 배워야 할 것이 많지만 Rx는 이러한 기본 원리를 기반으로 합니다. 잠쉬 휴식을 갖고 완전히 이해하기 위해서 이 개념들을 가지고 놀아보세요. 준비되셨으면 더욱 흥미로운 것들을 만나보러 가시죠!
3. Marble Diagrams🙌🏼
일반적으로 Rx, Rxswift를 사용해서 일을하려면 Marble Diagrams를 꼭 이해해야 합니다. Marble Diagram은 옵저버블 시퀀스를 시각화 한 것입니다. 정보가 들어오는 입구가 있고 배출되는 출구로 구성되어 있으며 실제로 함수 변환은 중간부에서 일어납니다.
예를 들어 옵저버블 시퀀스를 150ms만큼 이벤트를 지연시키는 동작을 수행했다고 가정합시다. scheduler 매개변수는 무시하세요 나중에 설명드릴게요
이해하기 쉽지 않나요?
모바일 단말기에서 이 다이어그램으로 상호작용할 수 있는 앱이 iOS와 Android용으로 있습니다. 장담컨데, 그것을 사용하면 Rx에 대해 더욱 많은 것을 배울 수 있을 것 입니다.
4. Transformations⚙️
변형을 하고 싶으시다면 구독자가 요소를 전달받기 전에 옵저버블 시퀀스에 combine이나 filter를 사용하세요. filter와 combine의 차이점을 구분하기 위해 간단한 변형 연산자들을 소개하겠습니다. 마지막에는 변형이나 조합이 다른 쓰레드에서 어떻게 수행되는지 보여드릴게요. 시작합니다.
4.1 Map
옵저버블 시퀀스에서 배출되는 요소를 변형하기 위해서는 구독자에게 전달되기 전에 map연산자를 사용해야 합니다. 시퀀스의 각 요소들이 배출되기 전에 10을 곱하는 것을 생각해보세요.
Observable<Int>.of(1,2,3,4).map { value in
return value * 10
}.subscribe(onNext:{
print($0)
})
OUTPUT: 10 20 30 40
4.2 FlatMap
옵저버블 시퀀스 객체로 구성된 옵저버블 시퀀스를 생각해보세요. 그리고 해당 객체들로부터 새로운 시퀀스를 만들고 싶을 때는 FlatMap을 사용하면 됩니다. FlatMap은 옵저버블의 결과들을 합쳐주고 그 결과를 시퀀스로 가집니다.
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)
})
OUTPUT: 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
버퍼 연산자는 해당 아이템들을 콜렉션 형태의 버퍼로 배출합니다.
SequenceThatEmitsWithDifferentIntervals
.buffer(timeSpan: 150, count: 3, scheduler:s)
.subscribe(onNext:{
print($0)
})
OUTPUT: [1] [2,3] [4] [5,6] [7] []
5. Filter🚬
만약 특정 조건을 만족하는 next event만 얻길 원한다면 filter 연산자를 사용해야 합니다.
5.1 Filter
기본적인 필터 연산자는 Swift의 등호화 비슷하게 동작합니다. 조건만 정의하면 조건을 충족하는 next event만 구독자에게 전달될 것입니다.
Observable.of(2,30,22,5,60,1).filter{$0 > 10}.subscribe(onNext:{
print($0)
})
OUTPUT: 30 22 60
5.2 DistinctUntilChanged
만약 값이 이전의 값과 비교하여 달라졌을 때만 next event를 받고 싶다면 distinctUntilChanged를 사용하면 됩니다.
Observable.of(1,2,2,1,3).distinctUntilChanged().subscribe(onNext:{
print($0)
})
OUTPUT: 1 2 1 3
다른 필터 연산자들도 확인해보세요
- Debounce
- TakeDuration
- Skip
6. Combine💑
시퀀스를 조합하는 것은 일반적인 작업입니다. RxSwift는 많은 연산자들을 제공하지만 여기서는 3개만 살펴보겠습니다.
6.1 StartWith
만약 특정 값으로 시퀀스의 결과 값을 시작하길 원한다면 startWith를 사용하세요
Observable.of(2,3).startWith(1).subscribe(onNext:{
print($0)
})
OUTPUT: 1 2 3
6.2 Merge
여러 개의 옵저버블의 결과물을 하나의 옵저버블의 결과처럼 조합할 수도 있습니다. Merge를 사용한다면요,
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)
OUTPUT: 20 40 60 1 80 2 100
6.3 Zip
Zip 메소드를 사용하면 다른 옵저버블 시퀀스를 하나의 옵저버블 시퀀스로 합칠 수 있습니다. Zip은 엄격한 시퀀스에서 동작해서 처음 두 개의 요소들은 a 시퀀스의 첫 번째 요소와 b 시컨스의 첫 번째 요소의 조합입니다. Zip 메소드는 여러개의 시퀀스가 있는 경우 가장 적은 수의 요소를 가진 시퀀스만큼 결과를 뱉어낸 다는 것을 꼭 기억하세요!
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)
}
OUTPUT: (1, "a")(2, "b") (3, "c") (4, "d")
다른 조합 필터도 확인해보세요.
- Concat
- CombineLatest
- SwitchLatests
7. Side Effects👉
만약 옵저버블 시퀀스의 특정 이벤트가 발생했을 때 실행되는 콜백을 등록하고 싶다면 doON연산자를 사용하세요. 해당 콜백은 배출되는 요소들은 수정하지 않습니다.
- do(onNext:) - next event가 발생했을 때 호출됩니다.
- do(onError:) - 에러가 배출되면 호출됩니다.
- do(onCompleted:) - 정상적으로 시퀀스가 종료되면 호출됩니다.
Observable.of(1,2,3,4,5).do(onNext: {
$0 * 10 // This has no effect on the actual subscription
}).subscribe(onNext:{
print($0)
})
8. Schedulers⏰
연산자들은 구독이 생성된 쓰레드와 동일한 곳에서 수행될 것입니다. RxSwift에서는 스케줄러를 사용해서 특정 큐에서 동작하도록 할 수 있습니다. 구독이 다른 특정 큐에서 발생하도록 강제할 수도 있습니다. subscribeOn과 observeOn가 해당 작업을 수행합니다. 만약 operation-queues와 dispatch-queues에 익숙하시다면 이런 개념은 낯설지 않을 것입니다. 스케줄러는 GCD나 OperationQueue와 비슷합니다.
- MainScheduler - MainThread.에서 수행되어야 하는 작업들을 말합니다. 메인스레드에서 호출된 Schedule 메소드들의 경우 스케줄링 없이 바로 수행될 것입니다. 이 스케줄러는 보통 UI 작업을 수행하는데 사용됩니다.
- CurrentThreadScheduler - 현재 스레드에서 작업을 하는 스케줄 유닛입니다. 요소들을 생성하는 기본 스케줄러 연산자들이 해당됩니다. 동시에 dispatch queue가 전달되어도 직렬로 변환되는 것을 보장합니다. 메인 스케줄러는 SerialDispatchQueueScheduler의 인스턴스 입니다.
- SerialDispatchQueueScheduler - 특정 dispatch_queue_t에서 수행되어야 할 작업들을 말합니다.
- ConcurrentDispatchQueueScheduler - 특정 dispatch_queue_t에서 수행되어야 할 작업들을 말합니다. 직렬 dispatch queue를 전달해도 문제가 발생하지 않습니다. 이 스케줄러는 백그라운드에서 작업이 수행되어야 할 때 적절하게 쓰입니다.
- OperationQueueScheduler - 특정 NSOperationQueue에서 수행되어야 할 작업들을 말합니다. 백그라운드에서 수행되어야 할 큰 덩어리의 작업을 수행할 때 적절하며 maxConcurrentOperationCount를 사용하여 동시 발생하는 프로세스의 수를 조정할 수 있습니다.
아래의 백그라운드 큐와 메인 큐에서 동시에 발생하는 일에 대해 보여주는 코드입니다.
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)
OUTPUT: 20 40
It’s a wrap🎁
축하합니다. RxSwift의 기본에 대해서 배우셨습니다. 즐거운 코딩되세요!🎉
Summary
- Rx, RxSwift는 모둔 값들을 Observable Sequence로 다루는 것이다.
- 시퀀스는 데이터의 흐름이며 특정 이벤트가 발생했을 때 값들을 뱉어준다.
- 필터와 컴바인 연산자를 통해 Subject의 값들이 subscribe에게 전달되기 전에 조작할 수 있다.