RxSwift 시즌 2 정리
영상
Swift에서 비동기(async)로 작업하는 경우 completion을 통해서 값을 가져오고 이 값을 사용할 때마다 계속 함수를 호출해야 했음
// 함수 선언
func downloadJson(_ url: String, completion: @escaping ((String?) -> Void)){
let url = URL(string: url)!
DispatchQueue.global().async{
let data = try! Data(contentsOf: url)
let json = String(data: data, encoding: .utf8)
completion(json)
}
}
// 사용
downloadJson(MY_URL){ json in
let result = json
}
이 어려움을 덜어주기 위해 등장한 것이 RxSwift 임
나중에 생기는 데이터 Observable로 선언하고 subscribe 후 Event(next / error / completed)가 발생
Observable을 생성 할 때는 create(), return 시에는 Disposables.create() 사용
위 방식과 다르게
1. 비동기로 생기는 데이터를 Observable로 감싸서 리턴
func downloadJson(_ url: String) -> Observable<String?>{
return Observable.create(){ emitter in
let url = URL(string: url)!
let task = URLSession.shared.dataTask(with: url){ data, response, error in
// onError
guard error == nil else{
emitter.onError(error!)
return
}
// onNext
if let data = data, let json = String(data: data, encoding: .utf8){
emitter.onNext(json)
}
// completed
emitter.onCompleted()
}
// task resume
task.resume()
// task cancel
return Disposables.create(){
task.cancel()
}
}
}
2. Observable로 오는 데이터를 받아서 처리
let observable = downloadJson(MEMBER_LIST_URL)
let disposable = observable.subscribe({ event in
switch(event){
case .next(let json):
print(json)
break
case .error(let error):
print(error)
break
case .completed:
print("Completed")
break
}
})
disposable.dispose()
Observable의 생성 주기는 create() → subscribe → onNext 일단 작업은 끝나고
결과는 onCompleted / onError로 나오고
모든 동작이 종료되는 Disposed, Disposed된 이후에는 재사용은 불가능함
2번의 데이터를 받아서 처리하는 방식을 간소화 할 수 있음
_ = downloadJson(MY_URL)
.subscribe(onNext: { print($0) },
onError: { error in print(error) },
onCompleted: { print("Completed") })
subscribe는 return 타입이 Disposable
Disposable 소멸 후에 Bag 에 담음 → DisposeBag
생성자를 통해 Observable 생성할 수 있음
- just : 데이터 1개만 전송
- from : 데이터 여러 개 전송
Operator는 observable과 subscribe 사이에 받아서 데이터를 중간에 바꿈
- map, filter 등등
- observeOn : observeOn 작성한 이후의 스케줄러 (DownStream)
- subscribeOn : 맨 처음 시작부터 사용되는 스케줄러 → 순서 상관 없음 (UpStream)
여기서 스케줄러는 Schedular 타입 → OperationQueue - scan(x, y) : x + y
_ = downloadJson(MEMBER_LIST_URL)
.map{ json in json?.count ?? 0 } // String -> Int (operator)
.filter{ cnt in cnt > 0} // cnt > 0 (operator)
.map{ "\($0)"} // Int -> String (operator)
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .default))
.observeOn(MainScheduler.instance) // super : operator(DispatchQueue.main)
.subscribe(onNext: { json in
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
})
Combining
- zip : 두 데이터가 하나씩 생성되면 쌍으로 전달, 쌍으로 만들지 못하면 생성되지 않음
- combineLastest : 가장 많이 사용함. zip과 유사한 기능. 쌍이 없어도 최근 것이랑 쌍을 만들어 내려감
- merge : 데이터를 순서대로 두 개 합치는데 데이터 타입이 서로 같아야함
let jsonObservalbe = downloadJson(MEMBER_LIST_URL)
let helloObservable = Observable.just("Hello World")
// Hello World과 json이 함께 출력
Observable.zip(jsonObservalbe, helloObservable) { $1 + "\n" + $0 }
RxCoca = RxSwift + UIKit
subscribe를 bind로 편리하게 사용할 수 있음
// subscribe 사용
viewModel.itemsCount
.map{ "\($0)" }
.observeOn(MainScheduler.instance)
.subscribe(onNext: {
self.itemCountLabel.text = $0
})
.disposed(by: disposeBag)
// bind(to:) 사용
viewModel.itemsCount
.map{ "\($0)" }
.observeOn(MainScheduler.instance)
.bind(to: itemCountLabel.rx.text)
.disposed(by: disposeBag)
Observable 값을 컨트롤 하기 위해 Subject를 사용함
Subject는 통제가 가능하면서도 Observable 기능(onNext/onError)도 함
observable 밖에서 데이터를 컨트롤 해서 새로운 값을 줄 수 있는 onNext로 하는 것을 한번에 Subject를 사용
- PublishSubject : 데이터 생성되면 subscribe 하는 애들한테 그대로 보내줌
- BehaviorSubject : 기본 값이 있고, 초기에 기본 값이 있다가 값이 바뀔 때 마다 내려줌
- asyncSubject : complete전까지 값을 안보내다가 complete 후에 값을 전달
- replaySubject : 처음 subscribe했을 때는 PublishSubject와 동작은 동일, 그 이후에 다시 subscribe하면 전에 있던 데이터 까지 모두 전달
Subject를 선언하고 RxCocoa로 사용하는 예제
// ViewModel에 선언
var menuObservable = BehaviorSubject<[Menu]>(value: [])
UITableViewDataSource 사용하지 않고 바로 viewDidLoad에서 bind
viewModel.menuObservable
.observeOn(MainScheduler.instance)
.bind(to: tableView.rx.items(cellIdentifier: cellId,
cellType: MenuItemTableViewCell.self)){ _, item, cell in
cell.title.text = item.name
cell.price.text = "\(item.price)"
cell.count.text = "\(item.count)"
cell.onChnage = { [weak self] increase in
self?.viewModel.changeCount(item: item, increase: increase)
}
}.disposed(by: disposeBag)
take : 실행 횟수
delay : 설정한 시간 뒤에 다음 작업 진행
.delay(.seconds(1), scheduler: MainScheduler.instance)
.take(1)
RxCocoa 사용 시에는 UI 작업은 Main Thread에서만 작업
→ observeOn(MainScheduler.instance) 무조건 추가해야함
에러 발생 시 catchErrorJustReturn 사용 하여 에러가 발생했을 때 예외처리
Main Thread에서 작업하고 예외처리를 동시에 할 수 있음 → Driver
// 기존
viewModel.itemsCount
.map{ "\($0)" }
.catchErrorJustReturn("")
.observeOn(MainScheduler.instance)
.bind(to: itemCountLabel.rx.text)
.disposed(by: disposeBag)
// Driver 사용
viewModel.itemsCount
.map{ "\($0)" }
.asDriver(onErrorJustReturn: "")
.drive(itemCountLabel.rx.text)
.disposed(by: disposeBag)
Subject가 Stream이 끊어지면 안됨, 끊어지지 않는 Subject → RxRelay
complete / error 가 발생하지 않고 오로지 next만 발생하고, next가 아닌 accept 사용
var menuObservable = BehaviorRelay<[Menu]>(value: [])
// onNext 대신 accept 사용
menuObservable.accept($0)
RxCocoa에서 UI 작업에 특화되게 사용함
RxSwift | RxCocoa |
Observable | Driver |
Subject | Relay |
'iOS > RxSwift' 카테고리의 다른 글
[RxSwift] TimeBasedOperator (0) | 2023.10.03 |
---|---|
[RxSwift] Combine Operator (0) | 2023.10.03 |
[RxSwift] Traits (0) | 2023.09.18 |
[RxSwift] Subject (0) | 2023.09.16 |
[RxSwift] Observable (0) | 2023.09.10 |