본문 링크 (Original Link)

[RxSwift] 스케줄러

2018.05.31

#

by RxSwift, translated by pilgwon

  1. Serial vs Concurrent Schedulers
  2. Custom schedulers
  3. Builtin schedulers

스케줄러는 작업을 위한 메커니즘을 추상화 합니다.

각각의 작업을 위한 메커니즘에는 현재 스레드, 디스패치 큐, 오퍼레이션 큐, 새로운 스레드, 스레드 풀이 포함되어 있습니다.

observeOnsubscribeOn은 스케줄러로 작동되는 대표적인 두 연산자입니다.

만약 다른 스케줄러에서 작업을 실행하고 싶다면 observeOn(scheduler) 연산자를 사용하면 됩니다.

아마도 여러분은 subscribeOn 보다는 observeOn를 더 자주 사용하시게 될 것입니다.

observeOn 연산자가 명쾌하게 지정하지 않아도, 작업은 해당 요소들이 생성된 스레드/스케줄러에서 실행될 것입니다.

다음은 observeOn 연산자를 사용하는 방법에 대한 예제입니다.

sequence1
  .observeOn(backgroundScheduler)
  .map { n in
      print("이건 백그라운드 스케줄러에서 실행될 것입니다.")
  }
  .observeOn(MainScheduler.instance)
  .map { n in
      print("이건 메인 스케줄러에서 실행될 것입니다.")
  }

만약 시퀀스 생성(subscribe 메소드)을 시작하고 특정 스케줄러에서 dispose하고 싶다면, subscribeOn(scheduler) 연산자를 사용하면 됩니다.

subscribeOn이 명쾌하게 알려주지 않더라도, subscribe 클로저(Observable.create에 넘겨진 클로저)는 subscribe(onNext:) 이나 subscribe가 호출된 곳과 동일한 스레드나 스케줄러에서 호출될 것입니다.

subscribeOn이 명쾌하게 알려주지 않을경우에도, dispose메소드는 초기화했던 스레드/스케줄러에서 호출될 것입니다.

요약하자면, 직접적으로 스케줄러가 선택되지 않아도, 위의 메소드들은 현재 스레드나 스케줄러에서 호출됩니다.

Serial vs Concurrent Schedulers

스케줄러는 정말로 어떤 것이든 될 수 있고, 시퀀스를 변형하는 모든 연산자들은 추가적인 암시적인 보증을 보관해야 하기 때문에, 어떤 종류의 스케줄러를 만들지는 아주 중요한 문제입니다.

스케줄러가 컨커런트할때는, Rx의 observeOnsubscribeOn 연산자는 모든것이 완벽하게 작동되도록 만들 것입니다.

만약 Rx가 순차적이라고 증명할 수 있는 스케줄러를 사용한다면, 그것은 추가적인 최적화가 가능하다는 뜻입니다.

그러한 최적화는 아직까지는 디스패치 큐 스케줄러를 위해서만 실행됩니다.

시리얼 디스패치 큐 스케줄러의 경우, observeOn은 그냥 간단한 dispatch_async호출로 최적화됩니다.

Custom schedulers

지금까지 설명한 스케줄러에 뿐만아니라, 여러분 자신만의 스케줄러를 작성할 수 있습니다.

만약 작업 실행이 즉시되는 스케줄러를 원한다면, ImmediateScheduler프로토콜을 이용해서 커스텀 스케줄러를 만들면 됩니다.

public protocol ImmediateScheduler {
    func schedule<StateType>(state: StateType, action: (/*ImmediateScheduler,*/ StateType) -> RxResult<Disposable>) -> RxResult<Disposable>
}

만약 시간 기반 연산자를 지원하는 새로운 스케줄러를 만들고 싶다면, Scheduler프로토콜을 구현하면 됩니다.

public protocol Scheduler: ImmediateScheduler {
    associatedtype TimeInterval
    associatedtype Time

    var now : Time {
        get
    }

    func scheduleRelative<StateType>(state: StateType, dueTime: TimeInterval, action: (StateType) -> RxResult<Disposable>) -> RxResult<Disposable>
}

주기적인 스케쥴링을 해주는 스케줄러의 경우엔, PeriodicScheduler프로토콜로 Rx를 구현하면 됩니다.

public protocol PeriodicScheduler : Scheduler {
    func schedulePeriodic<StateType>(state: StateType, startAfter: TimeInterval, period: TimeInterval, action: (StateType) -> StateType) -> RxResult<Disposable>
}

만약 스케줄러가 PeriodicScheduling을 지원하지 않는다면, Rx가 주기적인 스케쥴링을 투명하게 실행할 것입니다.

Builtin schedulers

Rx는 모든 종류의 스케줄러를 쓸 수 있습니다. 만약 스케줄러가 시리얼한 것이 증명되면 추가적인 최적화도 적용이 가능합니다.

다음은 현재 지원하는 스케줄러들입니다.

CurrentThreadScheduler (Serial scheduler)

현재 스레드에 있는 작업의 단위들을 스케쥴해줍니다. CurrentThreadScheduler는 요소를 생성하는 연산자의 기본으로 적용되는 스케줄러입니다.

이 스케줄러는 가끔씩 “트램펄린 스케줄러(trampoline scheduler)” 라고도 불립니다.

만약 CurrentThreadScheduler.instance.schedule(state) { }를 어떤 스레드에서 처음으로 호출했다면, 그 예정된 행동은 즉시 실행될 것이고 모든 재귀적 예정된 액션들이 임시로 저장되는 숨겨진 큐가 생성될 것입니다.

만약 콜 스택의 몇몇 부모 프레임이 이미 CurrentThreadScheduler.instance.schedule(state) { }를 실행중이라면, 예정된 액션은 저장되고 현재 실행중인 액션과 모든 전에 저장되었던 액션이 실행 종료되고 나서 실행될 것입니다.

MainScheduler (Serial scheduler)

MainThread에서 실행되어야 하는 추상적인 작업에서 사용합니다. schedule메소드가 메인 스레드에서 호출된 경우에, MainScheduler는 스케줄링 없이 액션을 실행할 것입니다.

이 스케줄러는 보통 UI 작업에서 쓰입니다.

SerialDispatchQueueScheduler (Serial scheduler)

특정 dispatch_queue_t에서 실행되어야 하는 추상적인 작업에서 사용합니다. 컨커런트 디스패치 큐에 전달된 경우에도 시리얼 디스패치 큐로 변환됩니다.

시리얼 스케줄러는 observeOn를 위한 특정 최적화를 가능하게 해줍니다.

메인 스케줄러는 SerialDispatchQueueScheduler의 인스턴스 중 하나입니다.

ConcurrentDispatchQueueScheduler (Concurrent scheduler)

특정 dispatch_queue_t에서 실행되어야 하는 추상적인 작업에서 사용합니다. 시리얼 디스패치 큐에도 보낼 수 있으며 아무 문제도 일으키지 않을 것입니다.

이 스케줄러는 어떤 작업이 백그라운드에서 실행되어야 할 때에 적합합니다.

OperationQueueScheduler (Concurrent scheduler)

특정 NSOperationQueue에서 실행되어야 하는 추상적인 작업에서 사용합니다.

이 스케줄러는 어떤 큰 덩어리의 작업이 있고 이 작업이 백그라운드에서 실행되어야 하며 여러분이 maxConcurrentOperationCount를 이용해서 컨커런트 처리과정을 미세 조정하고 싶을 때에 적합합니다.