앱/Swift

.subscribe(on:)과 .observe(on:)

사과먹는사람 2024. 3. 16. 20:45
728x90
728x90

 

작업할 때 옵저버블을 관찰하는 스케줄러를 다른 스케줄러로 돌릴 때가 있다. .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

 

 

728x90
반응형