🧐Scheduelr?
Swift에서 멀티스레딩은 주로 GCD를 통해 처리합니다. 반면 RxSwift에서는 Scheduelr라는 추상화된 개념을 사용합니다.
하지만 scheduler != thread
Scheduler와 쓰레드 관계
- 하나의 쓰레드에 여러개의 스케쥴러가 존재할 수 있고
- 여러개의 쓰레드에 하나의 스케쥴러가 존재할 수 있습니다.
Scheduler종류
CurrentThreadScheduelr
public class CurrentThreadScheduler : ImmediateSchedulerType {
public static let instance = CurrentThreadScheduler()
}
: 기본 스케쥴러로, Serial Schduler로 동작합니다.
MainScheduler
public final class MainScheduler : SerialDispatchQueueScheduler {
public static let instance = MainScheduler()
public static let asyncInstance = SerialDispatchQueueScheduler(serialQueue: DispatchQueue.main)
}
- UI작업에 사용되는 가장 일반적인 스케쥴러
- Serial Scheduler(GCD Main Queue 유사)
- instance: 동기 방식의 싱글톤 인스턴스(serial & sync)/ asyncInstance: 비동기방식의 싱글톤 인스턴스(serial & async)
- ObserveOn연산자에 최적화
SerialDispatchQueueScheduler & ConcurrentDispatchQueueScheduler
public class SerialDispatchQueueScheduler : SchedulerType { /* ... */ }
public class ConcurrentDispatchQueueScheduler: SchedulerType { /* ... */ }
- 백그라운드 작업이나 특정 디스패치 큐를 지정할 때 사용
- GCD 큐를 래핑한 형태로 볼 수도 있다.
OperationQueueScheduler
- 실행 순서 제어나 동시 실행 작업 수 제한이 필요할 때 사용
- OperationQueue 추상화 버전
Observable과 Scheduler의 관계
Rx에서 중요한 개념은 Observable이 구독되는 시점에 생성된다는 것이다.
// 이 코드만으로는 아무것도 실행되지 않습니다
Observable.of(1, 2, 3, 4, 5)
.map { $0 * 2 }
// subscribe 호출 시 Observable이 생성되고 실행됩니다
Observable.of(1, 2, 3, 4, 5)
.map { $0 * 2 }
.subscribe(onNext: { number in
print(number)
})
.disposed(by: disposeBag)
기본적으로 Observable은 별도 지정이 없으면 main thread에서 동작합니다. RxSwift에서는 두 가지 방법으로 스케줄러를 변경할 수 있습니다.
Scheduler 제어 연산자
observeOn
해당 연산자 이후의 체인에서 사용할 스케쥴러를 지정합니다. 위치가 중요한 연산자!
subscribeOn
Observable이 생성되는 스케쥴러를 지정합니다. 이 연산자는 체인 내 어디에 위치하든 동일한 효과를 가집니다. 자 예시로 넘어가서 더 이해해봅시당
let backgroundScheduler = ConcurrentDispatchQueueScheduler(queue: DispatchQueue.global())
Observable.of(1, 2)
.do { number in
if Thread.isMainThread {
print("[main]: do", number) // 실행됨 ✓
} else {
print("[not main]: do", number)
}
}
.compactMap({ number -> Int? in
if Thread.isMainThread {
print("[main]: compactMap", number) // 실행됨 ✓
} else {
print("[not main]: compactMap", number)
}
return number
})
.observe(on: backgroundScheduler) // 여기서부터 백그라운드 스케줄러 적용
.filter { number -> Bool in
if Thread.isMainThread {
print("[main]: filter", number)
} else {
print("[not main]: filter", number) // 실행됨 ✓
}
return true
}
.map { number -> Int in
if Thread.isMainThread {
print("[main]: map", number)
} else {
print("[not main]: map", number) // 실행됨 ✓
}
return number
}
.subscribe(on: MainScheduler.instance) // Observable 생성 스케줄러 지정
.subscribe(onNext: { number in
if Thread.isMainThread {
print("[main]: subscribe", number)
} else {
print("[not main]: subscribe", number) // 실행됨 ✓
}
})
.disposed(by: disposeBag)
observeOn이전의 연산자들(do, compactMap)은 subscribeOn에서 지정한 MainScheduelr에서 실행됩니다.
observeOn이후의 연산자들(filter. map, subscribe)은 backgroundScheduler에서 실행됩니다.
- 메인 스케쥴러에서는 메인스레드에서 직렬로 작업을 처리하겠죠. 즉 1에대한 모든 작업이 완료된 후에 2에 대한 작업을 시작할 것입니다. 예상 가능한 순차적 실행으로
- backgroundScheduler에서는 여러 쓰레드에서 동시에 작업을 처리합니다.
- 1의 Filter->Map->subscribe가 한 스레드에서 실행되는 동안 다른 쓰레드에서는 2의 작업이 병렬로 실행될수 있습니다. 즉 실행 순서가 항상 일정하지 않죠
🚩 Scheduler 사용 시 주의사항
- 적절한 스케줄러 선택:
- UI 작업 → MainScheduler
- 무거운 계산 → ConcurrentDispatchQueueScheduler
- API 호출, 파일 작업 → 백그라운드 스케줄러
- observeOn의 위치:
- 위치에 따라 전체 체인의 동작이 달라짐
- UI 업데이트 직전에 .observe(on: MainScheduler.instance)를 배치
- 스케줄러 전환 비용:
- 스케줄러 간 전환은 비용이 발생함
- 불필요한 전환은 피하고 필요한 위치에서만 사용
'반응형프로그래밍' 카테고리의 다른 글
RxSwift(8)-에러처리 (0) | 2025.05.08 |
---|---|
RxSwift(7)-BehaviorRelay (0) | 2025.05.06 |
RxSwift(6)-RxCocoa(bind, drive, DelegateProxy) (0) | 2025.05.06 |
RxSwift(5)-TimeBasedOperators (0) | 2025.05.04 |
RxSwift(4)-Combining Operators (0) | 2025.05.02 |