1. 왜 비동기 프로그래밍을 하는가?
생활 예로, 마트에서 물건을 살 때 계산 줄을 서는 것을 생각하면 쉽다.
먼저 온 순서대로 한사람 한사람씩 계산되는 과정이 '동기적 프로그래밍'이다.
여기서, 어떤 사람이 물건을 엄청 많이 샀다던가, 계산 중에 다른 물건을 집어온다거나 할 때, 뒤에 서있는 고객들은 그 문제의 사람이 계산을 마칠 때까지 기다리는 수밖에 없다.
이와 같이, 동기적으로 프로그래밍을 한다면 한 작업이 끝날 때까지 그 뒤의 어떤 작업도 진행되지 않기에, 화면 로딩, 통신 연결 등의 비효율을 높이고, 사용성 또한 급격히 떨어진다.
코드가 한 페이지에서 동기적으로만 일을 하게 된다면, 아마 페이지 최상단에 있는 것부터 하나씩 또 하나씩 로딩이 될 것이며, 설상가상 시작하면 중간에 물릴 수도 없으니, 화면을 로딩하는 중간에 다른 명령을 수행할 수도 없을 것이다.
만약 동영상 재생 한 페이지에서 하나씩하나씩 따로 로딩해서 동영상이 돌아가는데 꽤 오랜 시간이 걸리고, 그 시간 동안 다른 건 아무것도 클릭하지 못한다면 매우 불편할 것이다.
거기에 대한 해답이 바로 '비동기 프로그래밍'이다. 비동기적으로 코드를 실행하면 더 유동적으로, 더 효율적으로 많은 일을 할 수 있다!
동기적 일처리 방식 : 순차적으로 일을 스스로 끝내 나가는 방식 (한줄 한줄 쳐낸다)
비동기적 일처리 방식 : 해야 할 일을 위임하고 기다리는 방식 (동시다발적으로 업무를 수행하고 완료되는대로 loading되는 방식)
다시 정리하면, 유동적으로, 효율적으로 움직이게 하기 위해서는 비동기적 프로그래밍이 필요하다.
2. RxSwift 란
iOS의 비동기 프로그래밍에서, 나중에 들어오는 return값(Observable)을 나중에 데이터가 return해서 들어오면 처리(Subscribe) 하는 것을 간결하고 효율적으로 도와주는 프레임워크이다.
- 나중에생기는데이터<타입> = Observable<타입>
- 나중에생기면 = subscribe
3. RxSwift 사용 방법
1) 비동기로 생기는 데이터를 Observable로 감싸서 리턴하는 방법
func downloadJson(_ url:String) -> Observable<String?> {
//1. 비동기로 생기는 데이터를 Observable로 감싸서 리턴하는 방법
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 dat = data, let json = String(data: dat, encoding: .utf8){
emitter.onNext(json) //데이터 전달
}
emitter.onCompleted() //종료 -> Observable의 끝
}
task.resume()
return Disposables.create() {
task.cancel() //observable을 취소할 때 하는 동작들 정의
}
}
}
2) Observable로 오는 데이터를 받아서 처리하는 방법
@IBAction func onLoad() {
editView.text = ""
setVisibleWithAnimation(activityIndicator, true)
//* observable의 생명주기
//create -> subscribe -> next -> complete / error -> disposed
let ob = downloadJson(MEMBER_LIST_URL)
let disposable = ob.subscribe { event in
switch event {
case let .next(json):
DispatchQueue.main.async {
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
}
case .completed:
break
case .error:
break
}
}
disposable.dispose() //Observable이 시행중이든 아니든 즉시 작업 취소
}
* observable의 생명주기
- create
- subscribe
- onNext (데이터 전달)
- onCompleted / onError (실행 종료)
- disposed (동작이 끝난 Observable은 재사용할 수 없음 - 일회용임. 다시 subscribe해 동작을 시작해야 함)
* observable이 종료되는 시점
.completed, .error, .disposed
4. Sugar API - 연산자 (Operator)
3번의 긴 코드를 간결하게 만들어주는 친구!
_ = downloadJson(MEMBER_LIST_URL)
.subscribe { event in
switch event {
case let .next(json):
DispatchQueue.main.async {
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
}
case .completed: //완료되면 멈춤
break
case .error: //에러가 발생하면 멈춤
break
}
}
위 코드를, 아래와같이 간결하게 작성할 수 있음
//sugar API -> 위처럼 case를 다 나누지 않아도 onNext, onError만 가져와서 사용
_ = downloadJson(MEMBER_LIST_URL)
.subscribe(onNext: { json in
DispatchQueue.main.async {
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
}
})
다 외울수는 없으니, 그때그때 찾아보면 좋음
연산자 모아놓은 문서 : https://reactivex.io/documentation/operators.html
1) 자주 쓰는 연산자 모음
- Observable.just("데이터") : 데이터 하나 전달할 때 사용하는 연산자
- .from(["hello", "world"]) : 여러 데이터를 하나씩 전달할 때 사용하는 연산자
- .subscribe(onNext: { ... } ) : next, completed, error, dispose 중 필요한 것만 파라미터로 사용 한다.
- .observeOn() : 다음 줄부터 실행할 스레드를 지정
- downstream(아래로) 영향을 준다
- DispatchQueue.main.async { ... }를 사용하지 않고 한 줄로 해결
- 매개변수로는 Scheduler타입을 넣는다.
- 예를 들어 MainScheduler.instance, ConcurrentDispatchQueueScheduler(qos: .defalut) ..
- .subscribeOn() : 맨 처음 시작할 스레드를 지정
- 어디에서 호출하든 맨 처음 스레드를 지정함
- upstream(위로) 영향을 준다
- .map { ... },.filter { ... } ...
- .buffer : 여러 데이터가 있을 때 지정된 갯수만큼 묶어서 내려보내줌
- .scan : 직전 데이터와 새 데이터를 가지고 연산을 수행
- .take(횟수) : 지정 횟수만큼만 수행
- .debug() : Observable내에서 어떤 데이터가 전달되는지를 보여줌
- merge : 데이터 타입이 같은 여러개의 Observable을 하나의 observable로 만듦
- zip : 두 Observable의 데이터를 합쳐 쌍으로 만들어 보냄 ex. 1 2 3 4 5.. observable과 a b c d e observable -> 1a 2b 3c... observable
- combinelatest : zip과 비슷하나, 쌍을 만들 데이터가 없을 경우 이전 데이터를 가지고 쌍을 만들어서 내려줌
2) 마블 다이어그램
- 동그라미 = 데이터
- 네모 = 연산자
- 색깔 : 쓰레드가 달라짐을 의미함
- 가로 화살표 = Observable
- 세로줄 = complete
ex)
- array 데이터가 from operator를 거치면, Observable이 나오고, 데이터가 순서대로 전달이 되고나서 complete된다.
5. Subject
Observable처럼 subscribe해 값을 받아올 수 있고, 외부에서 값을 컨트롤할 수도 있는 객체
https://reactivex.io/documentation/subject.html
1) 왜 쓰는가?
- create 시점 뿐 아니라, 이미 만들어진 subject에 대해서도 값 변경 가능
- Observable과의 차이점 : 생성된 이후에도 외부에서 값을 바꿀 수 있다. (데이터 주입 가능)
- PublishSubject와 BehaviorSubject를 가장 많이 사용한다.
2) Subject 종류
- PublishSubject()
- 자신을 subscribe한 객체에게 데이터를 내려줌
- 이후 subscribe한 객체에게는 그 이후의 데이터를 내려줌
- BehaviorSubject(기본값)
- subscribe하면 기본값을 일단 내려주고 시작함
- 이후 subscribe한 객체에게는 가장 최근의 데이터 + 그 이후의 데이터를 내려줌
- AsyncSubject
- subscribe한 객체들이 있어도 데이터를 내려주지 않다가
- 자신이 complete되는 시점에 맨 마지막 데이터만 subscribe한 객체들에게 내려주고 끝남
- ReplaySubject
- 새롭게 subscribe한 객체에게 그동안 발생한 데이터 중 bufferSize 만큼의 최근 값들을 내려줌
💡 Stream 이란?
Observable의 input이 있고, subscribe하면서 output이 나오는 모든 일련의 흐름들을 스트림이라고 한다.
6. Rxcocoa란?
- RxSwift를 UIKit의 요소에 접목하여, RxSwift의 사용을 용이하게 할 수 있도록 도와주는 프레임워크
- UIKit요소.rx로 사용 가능
- .bind(to: 바인더) : subscribe(onNext: { ... } ) 대신 사용
- [weak self]를 사용하지 않아도 알아서 순환참조를 예방해줌
.bind(to: totalPrice.rx.text)
.bind(to: tableView.rx.items(cellIdentifier: cellID, cellType: MenuItemTableViewCell.self)){ index, item ,cell in cell.price.text = "\(item.price)" }
cell.count.text = "\(item.count)"
cell.title.text = item.name
💡 RxDataSource
- 위와 같이 구현할 때
1) 데이터가 추가/수정/삭제로 인해 바뀔 때마다 reloadData를 하기때문에 데이터가 사라지는 애니메이션을 주지 못해 UX적으로 좋지 않음
2) section을 다루기 까다로움
- 위 2가지 불편한 점을 해결해주는 아이! 애니메이션도 관리할 수 있고, section을 관리할 수도 있음
=> 이 친구는 별도의 글로 따로 다뤄보는걸로!
reference
https://limjs-dev.tistory.com/136
'🍎 iOS > RxSwift' 카테고리의 다른 글
[RxSwift, RxCocoa] 주요 Operator 특징 및 차이점 정리 (계속 업데이트 예정) (0) | 2023.03.16 |
---|---|
[RxSwift] Disposable, Dispose, DisposeBag (0) | 2023.03.16 |
[RxSwift] RxSwift + MVVM + Moya 적용 (feat. tableview binding) (0) | 2023.01.19 |