티스토리 뷰

Swift

Combine 뽀개기 4장: Scheduler

Esiwon 2023. 7. 26. 20:12

Scheduler

A protocol that defines when and how to execute a closure.
클로저를 실행하는 시기와 방법을 정의하는 프로토콜입니다.

Scheduler를 사용하는 이유는 우리가 구현한 클로저가 언제 실행될지, 무슨 쓰레드에서 실행될지 제어하기 위해서이다.

때문에 Combine에선

  • DispatchQueue
  • OperationQueue
  • RunLoop
  • ImmediateScheduler

위와 같은 Scheduler를 제공한다.

DispatchQueue, OperationQueue를 이용하여 우린 클로저가 무슨 쓰레드에서 실행될지 제어해줄 수 있고, RunLoop 통해 어떠한 RunLoop에서 실행될 지 결정해 줄 수 있다.

ImmediateScheduler는 생소하지만, 공식문서에 의하면, 동기 작업을 수행하기 위한 스케줄러라고 나와있다. ImmediateScheduler를 사용하면 즉시 작업을 실행시킬 수 있다고 한다.

Combine은 기본적으로 Scheduler 직접 지정하지 않아도 어떠한 기준으로 Scheduler를 설정한다.

그 기준은 요소가 생성된 쓰레드이다.

var bag = Set<AnyCancellable>()

let subject = CurrentValueSubject<Int, Never>(1)

subject
    .sink { value in
        if Thread.isMainThread {
            print("작업 \(value)는 메인 쓰레드에서 실행")
        } else {
            print("작업 \(value)는 글로벌 쓰레드에서 실행")
        }
    }.store(in: &bag)


DispatchQueue.global().async {
    subject.send(2)
}
작업 1는 메인 쓰레드에서 실행
작업 2는 글로벌 쓰레드에서 실행

위와 같이 1번째 작업은 구독과 동시에 메인에서 일어났고, 2번째 작업은 DispatchQueue.global() 내부에서 일어났다.

때문에 결과를 보면, 2번째 작업에 대한 Scheduler는 자동으로 글로벌 쓰레드에서 동작하는 것을 볼 수 있다.

하지만, 요소가 어디서 생성되든 상관없이 강제로 Scheduler를 전환하는 메서드가 있다.

정확히는 메서드 다음으로 오는 클로저의 Scheduler를 변경한다.

receive(on:)

var bag = Set<AnyCancellable>()

let subject = PassthroughSubject<Int, Never>()

subject
    .map { value -> Int in
        let thered = Thread.isMainThread ? "메인" : "글로벌"
        print("작업 \(value)는 receive(on:) 전엔 \(thered)")
        return value
    }
    .receive(on: DispatchQueue.main)
    .sink { value in
        if Thread.isMainThread {
            print("작업 \(value)는 receive(on:) 후엔 메인 쓰레드에서 실행")
        } else {
            print("작업 \(value)는 receive(on:) 후엔 글로벌 쓰레드에서 실행")
        }
    }.store(in: &bag)


DispatchQueue.global().async {
    subject.send(1)
}
작업 1는 receive(on:) 전엔 글로벌
작업 1는 receive(on:) 후엔 메인 쓰레드에서 실행

결과와 같이 receive(on:) 메서드 전엔 1번째 요소가 생성된 글로벌 쓰레드 이지만, receive(on:) 메서드 후엔 메인 쓰레드로 Scheduler가 변경되었다.

이런식으로 receive(on:) 메서드가 호출된 이후의 Scheduler를 변경할 때 사용할 수 있다.

subscribe(on:)

subscribe(on:)는 구독의 Scheduler를 제어하는 메서드이다.

var bag = Set<AnyCancellable>()

let subject = CurrentValueSubject<Int, Never>(1)

subject
    .subscribe(on: DispatchQueue.global())
    .sink { value in
        if Thread.isMainThread {
            print("작업 \(value)는 메인 쓰레드에서 실행")
        } else {
            print("작업 \(value)는 글로벌 쓰레드에서 실행")
        }
    }.store(in: &bag)

subject.send(2)
작업 1는 글로벌 쓰레드에서 실행
작업 2는 메인 쓰레드에서 실행

위 결과를 보면 1번째 작업은 글로벌에서 수행됐다.

하지만, 2번째 작업은 메인에서 수행됐다.

이것이 subscribe(on:)receive(on:)의 차이이다.

subscribe(on:)는 단지 구독을 무슨 Scheduler에서 시킬지를 제아한다.

.subscribe(on: DispatchQueue.global())로 주었기 때문에 구독은 글로벌 쓰레드에서 일어났다.

초기값인 1번째 작업은 구독과 동시에 일어나고, 때문에 글로벌 쓰레드에서 작업이 수행된다.

하지만 2번째 작업은 메인에서 수행됐다. 이는 맨 처음에 말한 Scheduler 기본적으로 요소가 생성된 Scheduler를 따른다.

구독이 무슨 쓰레드에서 되었든 상관없다.

또하나의 특징은 subscribe(on:)는 PuPublisher와 Subscriber가 무슨 Scheduler에서 구독이 되냐를 제어하기 때문에 receive(on:)와 다르게 위치가 상관없다.

var bag = Set<AnyCancellable>()

let subject = CurrentValueSubject<Int, Never>(1)

subject
    .map {_ in print(Thread.isMainThread)}
    .subscribe(on: DispatchQueue.global())
    .sink { value in
        print(Thread.isMainThread)
    }.store(in: &bag)
false
false

참고한 블로그

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함