.subscribe(on:)과 .observe(on:)
작업할 때 옵저버블을 관찰하는 스케줄러를 다른 스케줄러로 돌릴 때가 있다. .subscribe의 onNext에서 UI 변경 작업을 해줘야 할 때 메인으로 바꿔준다든가 할 때는 거의 .observe(on:)을 사용하곤 했다.
.subscribe(on:)과 확실한 차이점은 어떤 것이 있어서 .observe(on:)을 썼을까 하면, 명쾌하게 이야기하기 힘들었다.
자주 쓰는 것이 아니면 잊어버리다 보니 이번 기회에 정리하고자 한다.
빠른 정리
1. .subscribe(on:) : Observable이 생성되는 스케줄러 변경
2. .observe(on:) : Observable을 구독하는, ObserverType의 스케줄러 변경
.subscribe(on:) - 생성 스케줄러 변경
.subscribe(on:)은 Observable.create, 즉 옵저버블이 생성되는 스케줄러를 변경하는 데 쓰인다.
예를 들어, 통신을 담당하는 옵저버블을 만드는 스레드를 백그라운드 스레드, qos는 .utility로 지정하고 싶다면 이렇게 해준다.
myApi
.getData(
headers: headers,
queryItems: queryItems
)
.subscribe(on: ConcurrentDispatchQueueScheduler(qos: .utility))
override func getData(type: SearchType, httpMethod: HTTPMethod = .GET, headers: [String: String], queryItems: [String: String]) -> Observable<Response> {
guard let request = createURLRequest(type, httpMethod, headers, queryItems) else { return .empty() }
return provider.networkService.execute(request: request).asObservable()
}
func execute<T: Decodable>(request: URLRequest) -> Single<T> {
return Single.create { [weak self] single -> Disposable in
print(Thread.current) // <NSThread: 0x6000017b1980>{number = 11, name = (null)}
guard let self else { return Disposables.create() }
let task = self.urlSession.dataTask(with: request) { data, response, error in
print(Thread.current) // <NSThread: 0x60000174bb00>{number = 5, name = (null)}
// 생략
single(.success(decodedData))
}
task.resume()
return Disposables.create {
task.cancel()
}
}
}
통신하는 옵저버블은 백그라운드 스레드에서 생성된다. 사실 큰 실효성은 없는 게, single.success로 이벤트 보내는 부분이 이미 백그라운드 스레드긴 하다 -_- 그래도 적당한 예제라고 생각되어 골라봤다.
만약 .subscribe(on:)을 안 붙이고 executet시키면 dataTask 컴플리션 핸들러 바깥의 Thread.current는 보통 1번, 메인 스레드로 나오게 된다. (특수한 경우, 즉 이전에 메인 스레드에서 작업하지 않았다면 다른 스레드가 나올 수도 있다)
하지만 이 경우에는 Observable을 생성하는 부분을 백그라운드로 스레드로 하겠다고 명시적으로 넘겼으므로 메인 스레드가 아닌 다른 스레드에서 작업하는 것이다.
그럼 왜 dataTask 컴플리션 핸들러는 다른 스레드에서 동작할까?
컴플리션 핸들러가 동작하는 스레드를 변경하려면 URLSession을 생성할 때 delegateQueue를 지정해줘야 한다. 그렇지 않으면 URLSession이 자동으로 serial 백그라운드 큐에서 컴플리션 핸들러 스레드를 실행시킨다.
이것은 이 글에서 다루는 내용과 조금 동떨어져 있으므로 링크만 한다.
.observe(on:) - 구독 스케줄러 변경
보통 이렇게 검색했다면 UI에 업데이트가 되어야 할 텐데, 익히 알려져있다시피 UI 업데이트는 메인 스레드에서 이루어져야 한다.
이것도 옵저버블을 구독하는 쪽에서 .asDriver 해서 driver로 변경했다면 메인 스레드에서 이벤트 처리를 실행하니 상관없지만, 일반적인 .subscribe로 이벤트 처리를 한다면 강제종료 등으로 위험할 수 있는 상황이다.
그래서 요럴 때는 .observe(on:)을 사용하는 것이다. .subscribe(on:)이 Observable을 생성하는 스레드를 결정하는 반면 .observe(on:)은 생성된 Observable을 구독하는 부분에 대한 스케줄러를 변경하는 데 쓰인다.
옵저버블을 구독하고 이벤트에 따른 처리를 하는 스레드는 이 스레드로 하겠다 하는 선언이다.
이제 백그라운드 스레드에서 실행한 통신 결과를 메인 스레드에서 받아서 처리하려고 한다. 이럴 때 이렇게 사용하면 된다.
reactor.state.map { $0.myList }
.observe(on: MainScheduler.instance) // 아래의 .subscribe를 메인 스레드에서 하겠다고 선언
.subscribe(onNext: { [weak self] list in
print(Thread.current) // <_NSMainThread: 0x600001700180>{number = 1, name = main}
})
.disposed(by: disposeBag)
앞서 설명했듯 .asDriver로 변경하고 .observe(on:)을 하지 않을 수도 있다. (사실 UI 바인딩을 해야 한다면 driver로 변경하는 것이 더 적절하지 않나 생각하긴 한다)
하지만 구독을 꼭 메인 스케줄러에서 하라는 법은 없다. 다른 스케줄러에서 구독하고 처리할 수도 있는 것이다. 그런 경우에는 .observe(on:)을 유용하게 쓸 수 있다.
References