관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] RxSwift의 MainScheduler.instance와 MainScheduler.asyncInstance 차이 본문

iOS 응용 (swift)

[iOS - swift] RxSwift의 MainScheduler.instance와 MainScheduler.asyncInstance 차이

jake-kim 2023. 1. 23. 22:32

MainScheduler란?

// https://github.com/ReactiveX/RxSwift/blob/25d35f564b95ff4610b78f622c77ec3317aff31c/RxSwift/Schedulers/MainScheduler.swift#L24

Abstracts work that needs to be performed on `DispatchQueue.main`. In case `schedule` methods are called from `DispatchQueue.main`, it will perform action immediately without scheduling.
This scheduler is usually used to perform UI work.
Main scheduler is a specialization of `SerialDispatchQueueScheduler`.
This scheduler is optimized for `observeOn` operator. To ensure observable sequence is subscribed on main thread using `subscribeOn`
operator please use `ConcurrentMainScheduler` because it is more optimized for that purpose.
  • MainScheduler는 DispatchQueue.main를 wrapping한 구조이며 RxSwift에서 observe(on:)에서 사용
  • 만약 subscribe(on:)을 사용하고 싶은 경우는 MainScheduler가 아닌, ConurrentMainScheduler를 사용하는것이 최적화되어 있음을 주의

MainScheduler.instance와 MainScheduler.asyncInstance 차이

  • instance, asyncInstance 모두 싱글톤이며, MainScheduler 클래스 안에 존재
public final class MainScheduler : SerialDispatchQueueScheduler {
	...
    
    /// Singleton instance of `MainScheduler`
    public static let instance = MainScheduler()

    /// Singleton instance of `MainScheduler` that always schedules work asynchronously
    /// and doesn't perform optimizations for calls scheduled from main queue.
    public static let asyncInstance = SerialDispatchQueueScheduler(serialQueue: DispatchQueue.main)

	...
}
  • 위 코드를 보면 asyncInstance도 DispatchQueue.main을 사용하고, instance도 자신의 클래스 MainScheduler() 인스턴스를 생성하여 사용하는데, 이 안에서 mainQueue는 DispatchQueue.main을 사용
    public init() {
        self.mainQueue = DispatchQueue.main
        super.init(serialQueue: self.mainQueue)
    }
  • 그리고 MainScheduler 클래스는 SerialDispatchQueueScheduler를 상속받고 있는 상태이며, asyncInstance는 SerialDispatchQueueScheduler에 DispatchQueue.main인스턴스를 주입하여 생성
  • 둘 다 DispatchQueue.main 인스턴스를 사용하지만, 차이점은 instance는 MainScheduler 클래스의 인스턴스를 사용한다는 것
  • MainScheduler 클래스에서 SerialDispatchQueueScheduler의 메소드를 오버라이딩 하고 있는 것을 보면 차이점 확인이 가능

MainScheduler와 SerialDispatchQueueScheduler 동작 차이점

  • 둘 다 모두 생성자에서 DispatchQueue.main 인스턴스를 주입받아서 action에 대해서 scheduler() 하는 메소드가 있고 여기서 차이점 확인이 가능
  • schedule 동작은 작업의 갯수를 가지고 작업의 순서나 작업의 양을 제어하는 작업
  • 코드를 보기 전에 앞서, 결론부터 instance와 asyncInstance 차이는 intance는 바로 현재가 MainScheduler이면 queue.async {}로 실행하지 않고 바로 실행 시켜서 (sync)하게 동작되도록 하고 asyncInstance는 바로 async로 동작

1) MainScheduler.instance 동작

  • 현재가 main 스레드이고 이전 작업이 하나도 없으면 바로 sync하게 동작시키는 것 (if DispatchQueue.isMain &&... 부분의 코드)
    //  MainScheduler.swift
    
    override func scheduleInternal<StateType>(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable {
        let previousNumberEnqueued = increment(self.numberEnqueued)
	
        if DispatchQueue.isMain && previousNumberEnqueued == 0 {
            let disposable = action(state)
            decrement(self.numberEnqueued)
            return disposable
        }

        let cancel = SingleAssignmentDisposable()

        self.mainQueue.async {
            if !cancel.isDisposed {
                cancel.setDisposable(action(state))
            }

            decrement(self.numberEnqueued)
        }

        return cancel
    }

2) MainScheduler.asyncInstance 동작

  • 바로 async하게 동작
    func schedule<StateType>(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable {
        let cancel = SingleAssignmentDisposable()

        self.queue.async {
            if cancel.isDisposed {
                return
            }


            cancel.setDisposable(action(state))
        }

        return cancel
    }

결론

  • MainScheduler.instance는 바로 sync하게 동작하며, MainScheduler.asyncInstance는 바로 async하게 동작
  • 이미 메인스레드인 경우, instance는 동기적으로 이벤트를 바로 처리할 수 있고, asyncInstance는 비동기 이벤트 처리를 보장
  • 웬만하면 instance를 사용하되, 특이 케이스는 asyncInstance를 사용할 것 
    • 특이 케이스: 해당 스트림에서 재귀적인 접근이 동작할때, Rx 내부적으로 이런 동작은 async에서 처리하게끔 되어있어서 아래처럼 경고 메시지가 뜨는데 이 경우에 asyncIntance를 사용할 것
// https://github.com/RxSwiftCommunity/RxBiBinding/issues/20

⚠️ Reentrancy anomaly was detected.
Debugging: To debug this issue you can set a breakpoint in /Users/antonioscardigno/Documents/Sogetel/repository-forward/SOGETEL-RUMORS/DEV/iOSProjects/branches/newUI/Pods/RxSwift/RxSwift/Rx.swift:97 and observe the call stack.
Problem: This behavior is breaking the observable sequence grammar. next (error | completed)?
This behavior breaks the grammar because there is overlapping between sequence events.
Observable sequence is trying to send an event before sending of previous event has finished.
Interpretation: This could mean that there is some kind of unexpected cyclic dependency in your code,
or that the system is not behaving in the expected way.
Remedy: If this is the expected behavior this message can be suppressed by adding .observeOn(MainScheduler.asyncInstance)
or by enqueing sequence events in some other way.

* 참고

https://github.com/RxSwiftCommunity/RxBiBinding/issues/20

https://stackoverflow.com/questions/58332584/rxswift-mainscheduler-instance-vs-mainscheduler-asyncinstance

https://github.com/ReactiveX/RxSwift/blob/25d35f564b95ff4610b78f622c77ec3317aff31c/RxSwift/Schedulers/MainScheduler.swift#L36

https://github.com/ReactiveX/RxSwift/blob/25d35f564b95ff4610b78f622c77ec3317aff31c/RxSwift/Schedulers/MainScheduler.swift#L24

Comments