스케줄러 개념
- 작업을 어느 문맥(컨텍스트)에서 실행할지 결정한다.
- 다루기 어려운 스레드 개념을 스케줄러를 통해 구현 가능하게 해준다.
- 필요한 작업을 다른 스케줄러에서 실행하여 비동기로 동작할 수 있도록 한다.
- 별도로 스케줄러를 지정하지 않으면 현재 스레드에서 동작을 실행한다.
RxSwift 스케줄러 종류
MainScheduler
- UI를 변경하거나 높은 수준의 작업을 위한 스케줄러이다.
- 시간이 오래 걸리는 작업(네트워킹 등)은 여기서 실행되면 안된다.
- UI를 새로 그리거나 업데이트할 때에는 여기서 실행하여야 한다.
SerialDispatchQueueSchedular
- Serial 비동기 큐를 위해서 사용한다.
- 시리얼이기 때문에 작업의 순서가 보장되므로 네트워킹 등에 적합하다.
ConcurrentDispatchQueueSchedular
- Concurrent Queue로 동작하는 스케줄러이다.
- Serial을 막지 않도록 여러 개의 작업이 한꺼번에 실행되어야 할 경우 유용하다,
- 작업 완료 보장을 위해서는 concat()등과 같이 사용한다.
OperationQueueScheduler
- Concurrent 스케줄러와 비슷하나 DispatchQueue가 아닌 OperationQueue에서 동작한다.
- OperationQueue에서는 DispatchQueue보다 많은 옵션을 제공한다.
- concurrent로 한꺼번에 수행할 작업의 개수를 정할 수 있다.
TestScheduler
- 테스팅을 위한 스케줄러이다.
- 테스트를 위해 작업시간을 조정하여 옵저버블의 값이 언제 발행될지 조정할 수 있다.
스케줄링 연산자
subscribeOn()
- 이벤트를 발행하기 위한 옵저버블이 만들어지는 스케줄러를 지정한다.
- 옵저버블이 만들어지는 부분에 대한 지정이므로 체이닝 위아래로 영향을 미친다.
observeOn()
- 발행되는 이벤트를 처리하는 작업에 대해 어느 스케줄러에서 수행될지를 지정한다.
- 발행되는 이벤트를 사용하는 부분에 대한 지정이므로 체이닝 아래쪽으로 영향을 미치게 되고, 결과적으로 항상 subscribeOn()에 대해 우선된다.
- 대표적으로 데이터를 받아와 UI를 그리는 작업은 항상 메인 스레드에서 실행되어야 하기 때문에 .observeOn(Mainscheduler.instance)를 붙여준다.
let globalScheduler = ConcurrentDispatchQueueScheduler(qos: .default)
let mainScheduler = MainScheduler()
print("최상위 쓰레드: \(Thread.isMainThread ? "Main" : "Global")")
Observable<Int>.deferred {
print("옵저버블 생성: \(Thread.isMainThread ? "Main" : "Global")")
return Observable.just(100)
}
.map { num -> Int in
print(" Map 연산자: \(Thread.isMainThread ? "Main" : "Global")")
return num
}
.observe(on: mainScheduler) // 아래쪽 - subscribe() 스케줄러 지정
.subscribe(on: globalScheduler) // 전체 - Observable 생성(defer), map() 스케줄러 지정
.subscribe(onNext: { num in
print("구독: \(Thread.isMainThread ? "Main" : "Global")")
}).disposed(by: self.disposeBag)
/* 결과
최상위 쓰레드: Main
옵저버블 생성: Global
Map 연산자: Global
구독: Main
*/
콜백 지옥
func createRelation(email: String, completion: @escaping (_: String) -> Void) {
let url = URL(string: "https://api.eoflow.com/relations/v2")
AF.request(.post, url!, parameters: ["peerEmail": email])
.validate()
.responseJSON { response in
switch response.result {
case .success(let data):
guard let json = data as? [String: Any], let peerId = json["peerId"] as? String else { return }
completion(peerId)
case .failure(let error):
print(error)
}
}
}
func getPhoto(peerUserID: String, completion: @escaping (_: String) -> Void) {
let url = URL(string: "https://api.eoflow.com//relations/v2/peer-photo/\(peerUserID)")
AF.request(url!, method: .get)
.validate()
.responseJSON { response in
switch response.result {
case .success(let data):
guard let json = data as? [String: Any], let img = json["img"] as? String else { return }
completion(img)
case .failure(let error):
print(error)
}
}
}
func createAndGetPhoto(email: String) {
self.createRelation(email: email, completion: { id in
self.getPhoto(peerUserID: id, completion: { img in
self.setPhoto(img: img)
})
})
}
func setPhoto(img: String) {
let image = UIImage(data: img.data(using: .utf8)!)
}
Rx를 이용한 콜백지옥 벗어나기
func rxCreateRelation(email: String?) -> Observable<String?> {
guard let email = email else { return Observable.just(nil) }
let url = URL(string: "")
return Session().rx.request(.post, url!, parameters: ["peerEmail": email])
.validate()
.responseJSON()
.map { response -> String? in
switch response.result {
case .success(let data):
guard let json = data as? [String: Any], let peerId = json[""] as? String else { return nil }
return peerId
case .failure:
return nil
}
}
}
func rxGetPhoto(peerUserID: String?) -> Observable<String?> {
guard let peerUserID = peerUserID else { return Observable.just(nil) }
let url = URL(string: "https://api.eoflow.com//relations/v2/peer-photo/\(peerUserID)")
return Session().rx.request(.get, url!)
.validate()
.responseJSON()
.map { response -> String? in
switch response.result {
case .success(let data):
guard let json = data as? [String: Any], let img = json["img"] as? String else { return nil }
return img
case .failure:
return nil
}
}
}
func rxCreateAndGetPhoto(email: String) {
let source = PublishSubject<String>()
source
.concatMap { email in self.rxCreateRelation(email: email) }
.concatMap { peerUserID in self.rxGetPhoto(peerUserID: peerUserID) }
.subscribe(onNext: { img in
guard let img = img else { return }
self.setPhoto(img: img)
}).disposed(by: self.disposeBag)
source.onNext(email)
}