[RxSwift] 곰튀김님의 RxSwift 강의 (1)
RxSwift ?
RxSwift는 비동기적으로 생기는 데이터를 completion 같은 클로저를 통해 전달하는게 아닌 return 값으로 전달하기 위해서 만들어진 유틸리티이다.
completion을 사용한 비동기 처리 방식
private func downloadJson(_ url: String, _ completion: @escaping (String?) -> Void) {
DispatchQueue.global().async {
let url = URL(string: url)!
let data = try! Data(contentsOf: url)
let json = String(data: data, encoding: .utf8)
DispatchQueue.main.async {
completion(json)
}
}
}
@IBAction func onLoad() {
// completion을 사용한 비동기 처리 방식
editView.text = ""
self.setVisibleWithAnimation(self.activityIndicator, true)
downloadJson(MEMBER_LIST_URL) { json in
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
}
}
escaping을 사용하는 이유는 함수가 끝나고 나서 나중에 실행되기 때문에 escaping을 사용한다.
completion이 optional일 경우에는 escaping이 dafault이다.
completion을 사용하면서 비슷한 동작이 여러번 반복될 경우 콜백지옥을 볼 수 있다.
// 콜백 지옥 예시
@IBAction func onLoad() {
editView.text = ""
self.setVisibleWithAnimation(self.activityIndicator, true)
downloadJson(MEMBER_LIST_URL) { json in
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
downloadJson(MEMBER_LIST_URL) { json in
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
downloadJson(MEMBER_LIST_URL) { json in
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
downloadJson(MEMBER_LIST_URL) { json in
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
}
}
}
}
}
위 처럼 completion이 아닌 return으로 사용하면 좋겠다 싶어서 나온게 RxSwift!
RxSwift를 사용하여 바꿔보자.
RxSwift를 사용한 비동기 처리 방식
private func downloadJson(_ url: String) -> Observable<String?> {
return Observable.create { f in
DispatchQueue.global().async {
let url = URL(string: url)!
let data = try! Data(contentsOf: url)
let json = String(data: data, encoding: .utf8)
DispatchQueue.main.async {
f.onNext(json)
}
}
return Disposables.create()
}
}
@IBAction func onLoad() {
downloadJson(MEMBER_LIST_URL)
.subscribe { event in
switch event {
case .next(let json):
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
case .completed, .error(_):
break
}
}
}
Observable은 나중에 생기는 데이터
subscribe은 나중에 데이터가 오면 이라고 예시를 들어 강의를 진행하셨다.
Observable을 리턴하는 함수를 만들고 그 함수를 호출하는 부분에서 subcribe으로 구독하고있다.
URLSession과 함께 사용
private func downloadJson(_ url: String) -> Observable<String?> {
return Observable.create { emitter in
let url = URL(string: url)!
let task = URLSession.shared.dataTask(with: url) { (data, _, err) in
guard err == nil else {
emitter.onError(err!)
return
}
if let data = data,
let json = String(data: data, encoding: .utf8) {
emitter.onNext(json)
}
emitter.onCompleted()
}
task.resume()
return Disposables.create() { // Dispose가 되었을 때 실행 되는 블럭
task.cancel()
}
}
}
@IBAction func onLoad() {
editView.text = ""
self.setVisibleWithAnimation(self.activityIndicator, true)
downloadJson(MEMBER_LIST_URL)
.subscribe { event in
switch event {
case .next(let json):
DispatchQueue.main.async {
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
}
case .completed, .error(_):
break
}
}
}
URLSession을 이용해 통신을하고 Error가 나면 onError를 값을 잘 불러왔다면 onNext를 동작이 끝났다면 onCompleted를 사용한다.
Observable의 생명주기
- Create → Observable 생성
- Subscribe → 구독
- onNext → 값 전달
- onCompleted / onError → error로 전달되거나 completed가 되면 끝
- Disposed
Operator
기본적인 사용법을 알아봤으니 이제 귀찮은걸 덜어주는 유용한 기능들을 보자.
Just
func downloadString() -> Observable<String?> {
return Observable.create { f in
f.onNext("Hello Harry")
f.onCompleted()
return Disposables.create()
}
}
위에서 배운 내용대로 “Hello Harry” 라는 문자열을 Observable을 사용하여 값을 보내려면 위 처럼 코드를 작성해야한다.
Observable을 만들고, onNext로 값을 보내고, onCompleted로 완료하고 Disposables를 생성하여 리턴해야한다.
손이 많이가고 복잡하다.
func downloadString() -> Observable<String?> {
return Observable.just("Hello Harry")
}
just를 사용하면 4줄의 코드를 한줄로 변경할 수 있다.
데이터 하나를 보낼 때 just를 사용하면 간편하게 사용할 수 있다.
From
그렇다면 데이터 여러 개를 보내고 싶을때는 어떻게 할까?
func downloadString() -> Observable<[String?]> {
return Observable.just(["Hello", "Harry"])
}
위 처럼 just로 배열을 보내도 된다.
배열 요소 하나씩 보내고 싶다면 from을 사용한다.
func downloadString() -> Observable<String?> {
return Observable.from(["Hello", "Harry"])
}
Subscribe
downloadJson(MEMBER_LIST_URL)
.subscribe { event in
switch event {
case .next(let t):
print(t)
case .completed, .error(_):
break
}
}
단순히 print만 하는 코드인데 next, completed, error 처리를 다 해줘야해서 번거롭다.
이럴때 subscribe도 간단하게 작성할 수 있다.
downloadJson(MEMBER_LIST_URL)
.subscribe(onNext: { print($0) })
onNext일 때 print한다 라고 한 줄로 작성할 수 있다.
downloadJson(MEMBER_LIST_URL)
.subscribe(onNext: { print($0) },
onCompleted: { print("completed") },
onError: { err in print(err) })
위 처럼 사용할 수 있고, 내가 사용할 부분만 사용할 수 있다.
downloadJson(MEMBER_LIST_URL)
.observeOn(MainScheduler.instance) // operator
.subscribe(onNext: { json in
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
})
처음 예제에서 사용했던 subscribe을 바꿀 수 있다.
또 Main Thread로 변경해줘야 했던 부분을 observeOn(MainScheduler.instance)로 간단하게 사용할 수 있다.
데이터가 Observable에서 subscribe로 이동하는 중간에 데이터를 바꾸는 것을 operator라고 한다.
Operator
Map
downloadJson(MEMBER_LIST_URL)
.map { $0?.count ?? 0 }
.map { "\\($0)" }
.observeOn(MainScheduler.instance)
.subscribe(onNext: { json in
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
})
위 처럼 사용하면 데이터(json)의 count를 아래로 흘려보내고
editView.text에 넣기 위해 String으로 변환해서 아래로 흘려보낸다.
Filter
downloadJson(MEMBER_LIST_URL)
.map { $0?.count ?? 0 }
.filter { $0 > 0 }
.map { "\\($0)" }
.observeOn(MainScheduler.instance)
.subscribe(onNext: { json in
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
})
filter를 추가해 count가 0보다 크면 아래로 진행한다.
마블 다이어그램 (Marble Diagram)
ReactiveX 공식 사이트에 들어가면 많은 Operator들을 볼 수 있다.
위 사이트에서 Just에 대한 설명을 한번 봐보자.
위 사진이 Just의 마블 다이어그램이다.
구슬 → Data
중앙 박스 → Operator
하단 화살표 → Observable
하단 화살표 안 세로 막대 → Completed
Data를 Operator(Just)에 넣으면 Observable이 방출되고 Completed가 된다.
From도 한번 보자.
Array Data의 각 요소를 Operator(From)에 넣으면 순서대로 하나씩 Observable이 방출되고 모든 요소가 방출되고 나서 Completed가 된다.
Map도 보자.
Map은 위에도 Observable, 아래도 Observable이다.
위 데이터가 오면 map에서 지정한 연산을 수행하고 바뀐 Observable을 방출한다.
ObserveOn은 스레드를 관리하는 Operator였다. 한번 보자.
위에서 본 다른 Operator들과 비슷하지만 색상이 다르다.
여기서 색상은 스레드를 나타낸다.
ObserveOn 하고 스레드를 바꾸면 그 밑에부터 스레드가 바뀐다는 의미이다.
ObserveOn으로 스레드를 변경하고 Map이나 다른 Operator를 사용해도 스레드는 변하지 않는다.
단, subscribeOn을 사용하면 맨 처음 시작할 때 스레드를 지정해준다.
ObserveOn은 다음 줄부터 스레드를 변경하고 subscribeOn은 처음의 스레드를 변경한다.
곰튀김님이 아무거나 지정한 Operator
위 마블 다이어그램을 보면 Observable들이 지나가다가 completed 되는 시점에 마지막 Observable을 방출하는 것을 알 수 있다.
나도 아무거나 골라서 한번 봐보자.
last의 반대 first를 선택했다.
이름만봐도 기능이 짐작이 가지만 마블 다이어그램을 가져왔다.
첫 번째 Observable이 지나갈 때 방출되고 completed를 하는 것을 알 수 있다.
ReactiveX에 많은 Operator들이 있지만 마블 다이어그램을 보고 동작을 이해할 수 있다면 다 이해할 수 있다.
Operator는 여러 가지로 종류가 나뉜다.
- 데이터 생성
- 데이터 변형
- 데이터 필터링
- 여러가지 옵저버들을 묶어서 처리 (join, merge, zip 등)
- 에러 핸들링
- 유틸리티 등등
Stream의 분리 병합
Merge
merge의 마블 다이어그램을 먼저 보자.
merge는 여러개의 Observable을 하나로 합치는 것이다.
그래서 merge는 Observable의 데이터 타입이 같아야한다.
Zip
zip은 두 개의 Observable에서 데이터를 순서대로 하나의 쌍으로 만들어준다.
데이터 타입이 달라도 상관없다.
만들 쌍이 없다면 그냥 completed된다.
let jsonOb = downloadJson(MEMBER_LIST_URL)
let helloOb = Observable.just("Hello Harry")
Observable
.zip(jsonOb, helloOb) { $1 + "\\n" + $0 }
.observeOn(MainScheduler.instance)
.subscribe(onNext: { json in
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
})
// Hello Harry
// json ~~~~
CombineLatest
zip과 비슷하지만 zip은 쌍이 없으면 값이 방출되지 않지만 combineLatest는 가장 최근 데이터와 쌍을 만들어서 방출된다.
Operator에는 생성에 관련된 것도 있고, 데이터를 전달하는 과정에서 변형하거나 스레드 관리를 할 수있고, subscribe에서도 사용할 수 있었다.
이 처럼 RxSwift를 사용하면서 편리하게 데이터와 동작을 만질 수 있도록 도와주는 것을 Operator라고 한다.
DisposeBag
다운로드를 받는 중 화면에서 나가거나 중지하는 동작이 필요할 때 편리하게 사용하도록 만들어 둔 suger
var disposeBag = DisposeBag() // disposable들을 모아두는 가방
Observable
.zip(jsonOb, helloOb) { $1 + "\\n" + $0 }
.observeOn(MainScheduler.instance)
.subscribe(onNext: { json in
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
}).disposed(by: disposeBag) // .disposed(by: )로 가방에 담는다.
Reference