Combine
Combine은 비동기 및 이벤트 기반 코드를 처리하기 위한 프레임워크로, Publisher-Subscriber 패턴을 기반으로 한다.
Publisher 프로토콜은 시간이 지남에 따라 일련의 값을 전달할 수 있는 유형을 선언한다. 퍼블리셔에는 업스트림 퍼블리셔로부터 받은 값에 대해 작업하고 다시 퍼블리시하는 오퍼레이터가 있다.
퍼블리셔 체인의 끝에서 Subscriber는 요소를 수신할 때 해당 요소에 대해 작업을 수행한다. 퍼블리셔는 구독자가 명시적으로 요청하는 겨웅에만 값을 내보낸다.
Publisher
protocol Publisher {
associatedtype Output
associatedtype Failure: Error
func subscriber<S: Subscriber>(_ subscriber: S)
where S.Input == Output, S.Failure == Failure
}
퍼블리셔는 데이터를 생성하고 내보내는 역할을 한다. 이 데이터는 비동기적으로 생성될 수 있으며, 특정 시간 간격마다 발생할 수도 있다. Publisher는 내보내는 데이터의 유형과 함께 실패할 수 있는 오류 타입도 정의한다.
Built-in Publisher (내장 퍼블리셔)
- Just : 단일 값을 내보내는 가장 간단한 퍼블리셔이다. 주로 테스트 용도로 사용된다.
- Future : 나중에 값을 제공하는 퍼블리셔이다. 주로 비동기 작업의 결과를 Combine 스트림으로 포장할 때 사용된다.
- 추가로 iOS에서 기본적으로 제공되는 퍼블리셔 타입도 존재한다.
- NotificationCenter
- Timer
- URLSession.dataTask
Subcriber
protocol Subscriber {
associatedtype Input
associatedtype Failure: Error
func receive(subscription: Subscription)
func receive(_ input: Input) -> Subscribers.Demand
func receive(completion: Subscribers.Completion<Failure>)
}
구독자는 퍼블리셔에게 데이터를 요청하고, 이를 통해 데이터 스트림을 관리한다. 이때, 구독자는 퍼블리셔에게 요청할 데이터의 타입과 실패할 경우의 타입을 정의해야 한다.
구독을 한 후에는 구독자가 요청한 개수만큼 데이터를 받을 수 있다. 이렇게 함으로써 퍼블리셔는 구독자의 요청에 따라 적절한 양의 데이터를 생성하고 전달할 수 있다.
또한, 구독자는 파이프라인을 취소할 수 있다. 이는 퍼블리셔와의 연결을 해제하고 데이터 스트림을 종료하는 것을 의미한다. 이렇게 함으로써 불필요한 리소스 사용을 방지하고 메모리 누수를 방지할 수 있다.
Built-in Subscriber (내장 서브스크라이버)
- assign : 특정 객체의 키 경로에 퍼블리셔가 제공한 데이터를 할당한다. 이를 통해 퍼블리셔로부터 전달된 값을 객체의 속성에 바인딩할 수 있다.
- sink : 퍼블리셔가 제공한 데이터를 클로저를 통해 받을 수 있다. 이 클로저를 사용하면 퍼블리셔로부터 전달된 값에 대한 처리 및 로직을 수행할 수 있다.
Subscriber & Publisher Pattern
Subscription (구독)
Subscription은 Combine 프레임워크에서 Pusblisher와 Subscriber 사이의 연결을 나타낸다. 이 연결은 데이터 스트림을 통해 데이터를 수신하고 처리하는 데 사용된다. Subscription은 두 요소 간의 통신을 관리하며, 메모리 관리와 리소스 해제를 담당한다.
Subject (Pusblisher)
Subject는 Combine 프레임워크에서 Publisher와 Subscriber 역할을 모두 수행할 수 있는 특별한 유형의 Publisher이다. 즉, Subject는 데이터를 생성하고, Subscriber에게 전달할 수 있으며, 동시에 Subscriber로서 다른 Publisher를 구독할 수도 있다.
Built-in Subject
- PassthroughSubject : PassthroughSubject는 현재의 상태와 무관하게 외부에서 데이터를 제공할 떄 사용된다. PassthroughSubject는 구독한 Subscriber에게 현재부터 발생하는 이벤트를 전달한다.
- CurrentValueSubject : CurrentValueSubject는 초기값을 가지고 시작하며, 이후에 새로운 값을 내보낼 때마다 해당 값을 구독자에게 전달한다. 따라서 CurrentValueSubject는 현재 값을 유지하고 있는 상태를 유지하며, 이를 통해 최신 데이터를 제공할 수 있다.
@Published
@Published 속성은 ObservableObject 프로토콜을 따르는 클래스 내에 선언되며, 해당 속성의 값이 변경될 때마다 관련된 Publisher를 자동으로 발행한다. $를 이용해서 퍼블리셔에 접근할 수 있다.
class Weather {
@Published var temperature: Double
init(temperature: Double) {
self.temperature = temperature
}
}
let weather = Weather(temperature: 20)
let subscription = weather.$temperatur.sink {
print("Temperature now: \($0)")
}
weather.temperature = 25
// Temperature now: 20.0
// Temperature now: 25.0
Operator (연산자)
- 변환 연산자 : 데이터를 변환하거나 가공하는 데 사용된다.
- map
- flatMap
- scan
- 필터링 연산자 : 데이터 스트림에서 특정 조건을 만족하는 데이터만을 선택하는 데 사용된다.
- filter
- compactMap
- 결합 연산자 : 여러 데이터 스트림을 결합하여 하나의 데이터 스트림으로 합치는 데 사용된다.
- merge
- combineLatest
- zip
- 시간 연산자 : 시간과 관련된 작업을 수행하는 데 사용된다.
- debounce
- throttle
- delay
Scheduler (스케줄러)
Combine에서는 작업을 수행할 스레드나 큐를 관리하기 위해 스케줄러를 사용한다. 스케줄러는 Combine에서 비동기 작업을 제어하는 데 사용되며, 작업을 특정 스레드에서 실행하거나 작업을 지연시키는 등의 작업을 수행할 수 있다.
- subscriber(on:)을 이용해서, publisher가 어느 스레드에서 수행할 지 결정할 수 있다.
- 무거운 작업은 메인스레드가 아닌 다른 스레드에서 작업할 수 있게 도와준다.
- receive(on:)을 이용해서 operator, subscriber가 어느 스레드에서 수행할 지 결정할 수 있다.
- UI 업데이트에 필요한 데이터를 메인스레드에서 받을 수 있게 도와준다.
일반적인 패턴
let jsonPublisher = MyJSONLoaderPublisher() // Some publisher.
jsonPublisher
.subscribe(on: backgroundQueue)
.receive(on: RunLoop.main)
.sink { value in
label.text = value
}
UI 업데이트 시
// 이렇게 쓰기보단
pub.sink {
DispatchQueue.main.async {
// Do update ui
}
}
// 이렇게 쓸 것
pub.receive(on: DispatchQueue.main).sink {
// Do update ui
}