Search

RxSwift 4시간에 끝내기(1)

“RxSwift 4시간에 끝내기 (종합편)” 영상을 바탕으로 정리를 진행했습니다. 영상을 보고 싶으신 분들은 하단 링크를 참고해주세요.

 RxSwift

RxSwift는 ReactiveX 시리즈 중 하나입니다.
ReactiveX는 Observable Stream를 가지고 Asynchronous Programming 하기 위한 API 입니다.
RxSwift의 주요 기능은 Observable, Operator, Single, Subject, Schedular 가 있습니다.
Observable를 알면 RxSwift를 다 사용할 수 있고, Operator를 알면 RxSwift를 더 잘 쓸 수 있으며, Schedular를 알면 여러 군데서 활동 가능하게 할 수 있고, Subject를 알면 내가 뭔가를 만들 수 있게 됩니다.
ReactiveX는 하나의 언어에서만 알면 다른 언어에서도 사용 가능합니다.

 비동기 작업과 Observable

동기와 비동기는 뭘까요?
동기 : 작업 시에 다른 작업들이 멈추고 끝날 때까지 다른 작업을 못하게 막음
비동기 : 작업 시에 다른 작업과 상관없이 병행해서 진행
그렇다면, 동기 작업을 비동기로 만드려면 어떻게 해야 할까요?
DispatchQueue를 사용하면 됩니다. DispatchQueue에는 Sync, Async, Concurrent, Serial 총 4가지 방식이 존재합니다.
만약, DispatchQueue.global.async { } 로 코드를 작성했다고 칩시다.
global은 Concurrent Queue입니다. 그리고 Async 방식으로 코드 블록이 실행되도록 했네요. 즉, Concurrent(동시), Async(비동기) 실행을 합니다.
DispatchQueue.main.async { } 로 코드를 작성했다고 칩시다.
main은 Serial Queue입니다. 그리고 Async 방식으로 코드 블록이 실행되도록 했네요. 즉, Serial(직렬), Async(비동기) 실행을 합니다.
비동기 처리는 어렵기 때문에 이런걸 도와주는 Utility들이 존재합니다.
그게 바로, PromiseKit, RxSwift, Bolt 같은 라이브러리 입니다.
라이브러리들은 Async하게 데이터를 가져와야 하는데 그 Async한 코드를 간결하게 쓰게 해주는 Utility입니다. 비동기 코드를 간결하게 사용하고 싶다는 생각이 들면 RxSwift를 사용하면 됩니다.
DispatchQueue를 사용하면 되는데, 왜 어렵게 RxSwift를 쓰나요?
예를 들어서, 이런 상황이 있다고 생각해봅시다.
SNS 로그인을 하는데, 페이스북 로그인을 요청하고 그 요청 결과를 받습니다. 그리고 해당 요청 결과를 우리 서버에 던져줍니다. 그리고서, 서버로부터 온 결과를 받아서 해당 결과와 페이스북으로부터 온 결과 모두 true일 시에 실제 로그인을 처리해주는 겁니다.
위의 작업들이 async하게 돌아가야 합니다. 어떻게 비동기로 돌아가게 만들 수 있을까요?
이런 복잡한 작업에서 RxSwift를 사용하면 비동기 작업으로 만들기 쉽습니다. 하지만, RxSwift는 비동기 스트림을 만들어 줄 뿐만 아니라 Operator를 제공해줍니다. Operator가 RxSwift를 더 특별하게 만들어 줍니다.

 Disposable, DisposeBag

우리가 Observable에 Subscribe를 하면 Disposable이라는 게 return됩니다.
Disposable을 사용하면 cancel이 가능합니다. cancel이 가능하다는 건 Observable이 동작하고 있는 동안에 작업을 dispose 함으로써 취소시킬 수 있다는 의미입니다.
disposable.dispose()
Swift
복사
예를 들어서, 위의 로그인 과정 중 에러가 났다고 해봅시다. 그런 경우에는 나머지 작업을 다 취소시켜야 합니다.
또 다른 경우로, 로딩을 하는 중간에 뒤로 가기를 한다고 해봅시다. 이 경우에도 진행하던 작업을 다 취소 시켜야 합니다.
Disposable을 사용하면 한꺼번에 취소할 수 있습니다.
만약, 같은 기능을 RxSwift 없이 만든다고 하면 어떻게 구현해야 할까요?
OperationQueue를 사용해서 만들거나, Cancel flag를 만들어서 취소를 할 시에 flag를 바꿔주고 작업을 멈추는 식으로 구현할 수도 있겠네요.
하지만, RxSwift를 사용하면 Disposable을 통해서 귀찮은 작업들을 한 방에 다 해줍니다. Utility를 사용하면 구현이 편해지는 겁니다.
DisposeBag은 Disposable을 담는 가방입니다.
Observable의 Subscribe로부터 나온 disposable을 disposeBag에 담아보겠습니다.
var disposeBag = DisposeBag() let disposable = Observable.just("") .subscribe({ result in ... }) disposeBag.insert(disposable)
Swift
복사
disposeBag안에는 disposable을 여러개 담을 수 있겠네요. 그러면, 이번엔 백 안에 담긴 disposable을 한꺼번에 없애 봅시다.
하지만, DisposeBag에서는 dispose 메서드가 제공되지 않습니다.
그러면, 어떻게 한꺼번에 disposable를 지울 수가 있나요?
새로 만들어서 이전에 있는걸 지우는 방식을 사용하면 됩니다.
disposeBag = DisposeBag()
Swift
복사
새로운 DisposeBag를 넣어주면 기존에 담긴 disposable은 모두 dispose 됩니다.
어차피, subscribe에서 disposable이 나오니깐 .disposed(by: disposeBag) 으로 연결해서 작성해주면 더 쉽게 disposeBag에 disposable를 담을 수 있습니다.
var disposeBag = DisposeBag() Observable.just("") .subscribe({ result in ... }) .disposed(by: disposeBag)
Swift
복사

 기본 Operators

Observable를 create 해주는 메서드 중 하나가 바로, just() 입니다.
() 안에 뭘 넣어주면 해당 데이터가 전달됩니다.
Observable.just("Hello world") .subscribe(onNext: { str in print(str) }) .disposed(by: disposeBag) // Hello world
Swift
복사
위의 코드에서는 “Hello world”가 subscribe에 인자로 내려가게 됩니다.
만약, 배열을 넣는다고 한다면 배열이 그대로 나오게 됩니다. just는 데이터를 넣으면 넣는 그대로 나오게 합니다.
Observable.just([1, 2, 3, 4, 5]) .subscribe(onNext: { str in print(str) }) .disposed(by: disposeBag) // [1, 2, 3, 4, 5]
Swift
복사
from은 배열에 있는 것들을 하나씩 떼어내서 보내줍니다.
Observable.from([1, 2, 3, 4, 5]) .subscribe(onNext: { str in print(str) }) .disposed(by: disposeBag) // 1 // 2 // 3 // 4 // 5
Swift
복사
subscribe로 내려가는 중간에 map이 나타나면 map으로 먼저 전달됩니다. map 에서 가공한 데이터가 밑으로 내려가게 됩니다.
Observable.just("Hello world") .map { str in "\(str)!" } .subscribe(onNext: { str in print(str) }) .disposed(by: disposeBag) // Hello world!
Swift
복사
subscribe로 내려가는 중간에 filter가 나타나면 filter로 먼저 전달됩니다. filter 에서 걸러진 데이터가 밑으로 내려가게 됩니다. filter의 흐름은 true면 계속 내려가고, false면 내려가지 않습니다.
Observable.from([1, 2, 3, 4, 5]) .filter { $0 % 2 == 0 } .subscribe(onNext: { str in print(str) }) .disposed(by: disposeBag) // 2 // 4
Swift
복사
ReactiveX는 순서대로 실행이 됩니다.
데이터가 들어가고 Operator를 거쳐서 subscribe로 들어가서 disposable로 나오게 되는거죠. 데이터가 흘러가게 됩니다. 데이터가 줄 세워서 들어가고 중간 중간 여러 곳을 거치고 나오는 모습을 Stream이라고 합니다.
Stream를 사용해서 비동기 프로그래밍을 하겠다는게 ReactiveX 입니다.

 Operators의 종류들

위에서 just, from, map, filter 에 대해서 알아 봤습니다.
Operator들은 자신들만의 카테고리를 가집니다.
생성
create, just, from은 생성 연산자 입니다. 이 연산자를 사용해서 Observable를 만들어 냅니다.
변환
map은 변환 연산자 입니다. 이 연산자를 사용해서 데이터를 변환합니다.
필터링
filter는 필터링 연산자 입니다. 이 연산자를 사용해서 데이터를 걸러냅니다.
결합
결합 연산자는 결과를 합칠 때 사용합니다.
이전에 SNS 로그인 예시를 든 적이 있습니다. 우리 서버와 페이스북 로그인 모두 true 값을 넘겨야지만 진행하는 예시에서 결과를 합칠 때 사용할 수 있습니다.
오류 처리
오류 처리 연산자를 사용해서 중간에 에러가 발생했을 때 이를 처리합니다.
예를 들어서, 서버에 3번까지 다시 시도를 하고 그래도 안되면 실패되었다고 알럿을 보내려고 합니다. 이런 기능을 구현할 수 있게 도와주는것이 오류 처리 연산자 입니다.
위의 카테고리 외에도 많은 연산자들이 존재합니다.
ReactiveX 사이트에서 Operator 부분에 들어가보면, 많은 Operator들이 우리를 기다리고 있습니다.
그렇다면, 우리는 이 Operator들을 다 외워야 할까요?
아닙니다. 필요할 때 와서 찾아서 사용하면 됩니다.
못 찾겠다면?
Operator 영역 하단에 있는 A Decision Tree of Observable Operators를 보면 됩니다.
ReactiveX에서는 Operator를 이해하기 쉽게 Marble Diagram를 제공합니다.

 Marble Diagram 이해하기

이 그림이 바로 Marble Diagram 입니다.
다이어그램에서 4가지 요소만 기억하면 됩니다.
데이터
위의 다이어그램에서 보이는 빨간색 동그라미가 바로 데이터 입니다. 데이터는 동그라미가 아니라 다른 모양으로 나타날 수도 있습니다.
스트림
스트림은 화살표 모양으로 나타납니다.
Just 연산자는 Observable를 생성하는 연산자입니다. 따라서, 첫번째 데이터는 스트림이 없고 Just를 거친 데이터는 스트림 위에 올라가 있습니다. 화살표를 보면 스트림이라고 이해하면 됩니다.
Completed
스트림 위에 스트림과 수직 방향으로 그어져 있는 선이 바로 Completed 상태를 나타냅니다. onCompleted가 발생했다는걸 알 수 있습니다.
Error
스트림 위에 그어져 있는 엑스 표시가 바로 Error 상태를 나타냅니다.
onError가 발생했다는걸 알 수 있습니다.
Operator 설명에 들어가면 주요 Operator 하단에 얇은 글씨로 추가로 제공된 Operator들을 볼 수 있습니다.
또한, 언어별로 구체적인 Operator 관련 정보를 제공해줍니다. 하지만, RxSwift는 내부에 설명이 없습니다. 따라서, 설명을 보고 싶다면 RxJava 1.x를 참고하시면 됩니다.

 Next / Error / Completed

Observable.just("Hello world") .map { str in "\(str)!" } .subscribe(onNext: { str in print(str) }) .disposed(by: disposeBag)
Swift
복사
Observable.just()를 통해서 스트림이 만들어 집니다.
그리고, Stream에 적용할 수 있는 Operator들을 통해서 데이터가 가공됩니다. 가공된 데이터는 subscribe로 들어갑니다.
subscribe로 들어갔다는건 “최종으로 그 데이터를 사용할거야!”라는 뜻입니다. 따라서, subscribe는 모든 Operator들을 거친 최종적인 데이터를 받아서 사용하게 됩니다.
물론, 데이터가 들어갔다고 해서 해당 데이터를 받아서 사용할 필요는 없습니다.
그런 경우에는 하단 코드처럼 작성해주면 됩니다. 이 경우에는 실행만 시키고 그 결과는 받지 않습니다.
Observable.just("Hello world") .subscribe() .disposed(by: disposeBag)
Swift
복사
실행만 시키고 결과는 받지 않는 경우가 있나요?
예를 들어서, Firebase Analytics를 연결한다고 칩시다. 우리는 결과가 수집이 되든 말든 상관이 없습니다. 이런 경우에는 subscribe()만 하는 겁니다.
“아니, 나는 결과를 받아서 처리해야만 해!”라고 한다면, subscribe(on: _)를 사용하면 됩니다.
Observable.just("Hello world") .subscribe({ event in switch event { case .next(let data): print(data) case .error(let error): print(error.localizedDescription) case .completed: print("Completed") } }) .disposed(by: disposeBag)
Swift
복사
subscribe를 통해서 들어온 이벤트는 3가지 타입을 가집니다.
1.
next : 데이터 전달
2.
error
3.
completed : 스트림 완료
3가지 타입 중 error와 completed가 발생하면 Stream이 종료됩니다. next는 Stream를 종료시키진 않고, 데이터를 전달받습니다.
next로 데이터를 전달받는다구요?
Observable.from([1, 2, 3, 4, 5]) .subscribe(onNext: { str in print(str) }) .disposed(by: disposeBag)
Swift
복사
위의 코드를 실행하면 from으로부터 배열의 원소가 하나씩 전달됩니다. 하나씩 전달된 원소는 next로 데이터를 전달합니다. 즉, from를 하게 되면 배열의 count만큼 subscribe에서 next가 불립니다.
만약, 정상적으로 스트림이 진행된다면 이런 모습을 보입니다.
next로 데이터를 모두 전달받고서 잘 끝나면 마지막에 Completed가 불리게 되는거죠.
만약, 중간에 문제가 생긴다면 이런 모습일 겁니다.
문제가 생기면 마지막에 Error가 불리게 됩니다. 그리고 Error가 났기 때문에 Stream이 끝나게 됩니다.
근데, Completed와 Error를 모두 커버할 수 있는 상태는 없을까요?
예를 들어서, 다운로드가 완료되면 로딩 인디케이터를 hidden 시키려고 합니다. completed 상태에 로딩 인디케이터를 hidden하는 코드가 적으면 됩니다.
근데, 이런 경우에 문제가 발생하게 됩니다. 에러가 발생했을 때는 hidden이 안되는 겁니다.
그러면, 우리는 error 상태에 또 인디케이터를 hidden 하는 코드를 적을 겁니다. 즉, 중복되는 코드가 작성됩니다. 이 문제를 해결할 방법은 없을까요?
바로, disposed 상태를 사용하는 겁니다.
disposed는 error, completed 시에 모두 불리게 됩니다. 즉, Stream이 끝났을 때 사용할 코드를 모두 disposed 상태에서 다룰 수 있게 됩니다.
disposed는 아까 봤던 subscribe(on:_)에서는 사용할 수 없습니다. subscribe(onNext:_) 라는 메서드를 사용해야 합니다.
Observable.just("Hello world") .subscribe(onNext: { str in }, onError: { error in }, onCompleted: { }, onDisposed: { }) .disposed(by: disposeBag)
Swift
복사
하지만, 내가 사용하고 싶은 케이스가 onDisposed 뿐인데 다른 상태들까지 다 적어줘야 하나요?
아닙니다. 위의 subscribe에서 onDisposed 상태만 사용하고 싶다면 해당 상태만 구현해주면 됩니다.
모든 상태는 optional이기 때문에 원하는 상태만 골라서 처리해줄 수 있는겁니다.