
이번 포스트에서는 Combine 프레임워크에서 생성할 수 있는 다양한 형태의 Publisher를 알아보려고 한다.
Just는 가장 단순한 형태의 Publisher로 하나의 값을 방출하고 종료된다. 또한 에러를 방출하지 않기 때문에 Just의 Error 타입은 Never이다.
import Combine
Just(1)
.sink { completion in
print(completion)
} receiveValue: { value in
print(value)
}
// 1
// finished
Future는 의미 그대로 미래에 생성되는 값 또는 에러를 방출하는 Publisher이다. 미래에 생성되는 값의 예로 네트워크 통신의 결과 등을 예로 들 수 있다. 레거시 형태의 Completion Handler를 Combine Framework에 도입하는 경우 결합하여 사용할 수 있다.
Just와 마찬가지로 promise를 통해 하나의 값을 전달한 뒤 바로 종료된다.
import Combine
import Foundation
func generateAsyncRandomNumber() -> AnyPublisher<Int, Never> {
return Future { promise in
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
let randomNumber = Int.random(in: 1..<100)
promise(.success(randomNumber))
}
}
.eraseToAnyPublisher()
}
let cancellable = generateAsyncRandomNumber()
.sink { completion in
switch completion {
case .finished:
print("finish")
case .failure(let error):
print("error: \(error)")
}
} receiveValue: { value in
print(value)
}
//89
//finish
Fail은 에러를 발생시키고 바로 종료되는 Publisher이다.
import Combine
enum SomeError: Error {
case someError
}
Fail(error: SomeError.someError)
.sink {
print($0)
} receiveValue: { value in
print(value)
}
//failure(__lldb_expr_18.SomeError.someError)
아무 값도 방출하지 않는 Publisher이다. Empty 생성 시 즉시 종료할지 옵션으로 선택할 수 있다.
import Combine
Empty<Int, Error>(completeImmediately: true)
.sink(receiveCompletion: {
print($0)
}, receiveValue: {
//아무것도 받지 않음
print($0)
})
//finished
Deferred는 구독 전까지 기다렸다가 구독이 발생하면 Publisher를 반환한다. 다시말해, Deffered 생성자로 전달되는 클로저는 구독이 발생하면 Publisher를 반환하고 이 Publisher는 값을 지연 방출한다.
import Combine
let publisher = Deferred { Just(0) }
publisher
.sink(receiveCompletion: {
print($0)
}, receiveValue: { value in
print(value)
})
//0
//finished
Subject는 일종의 Publisher이다. 따라서 아래의 protocol 선언에서도 Publisher를 따르고 있는 모습을 확인할 수 있다.
protocol Subject<Output, Failure> : AnyObject, Publisher
그럼 Subject가 위에서 살펴본 Convenience Publisher와 다른 점은 무엇일까? Subject의 특징은 외부에서 값을 데이터 스트림에 주입할 수 있는 데에 있다. 다시 말해 외부에서 주입한 값을 방출하는 Publisher라고 할 수 있다.
이때 외부에서 값을 주입하기 위해 send(_:) 메서드를 사용하여 값을 전달하고, Subject는 해당 값을 방출할 수 있다.
지금부터 Subject protocol을 따르는 두 가지의 Subject를 알아보자.
CurrentValueSubject는 단일 값을 래핑하고 값이 변경될 때마다 값을 방출하는 Subject이다. CurrentValueSubject는 버퍼를 가지기 때문에 구독이 발생하면 해당 버퍼의 단일 값을 방출한다. 또한 버퍼에는 항상 값이 존재해야 하기 때문에 인스턴스 생성 시 초기 값을 전달한다.
import Combine
let subject: CurrentValueSubject<Int, Never> = .init(1)
let cancellable: AnyCancellable = subject.sink(receiveValue: {
print($0)
})
subject.send(2)
subject.send(completion: .finished)
subject.send(3) // 스트림이 종료되었기 때문에 방출 안 함
// 1
// 2
위의 코드와 같이 구독 시작과 함께 초기 값이 방출되고, 외부에서 2라는 값을 주입하면, CurrentValueSubject는 해당 값을 방출한다. 하지만 스트림이 완료된 후에는 추가적인 값(3)을 보내더라도 더 이상 방출되지 않는다.
PassthroughSubject는 초기값이 없이 외부에서 스트림에 값을 주입하면, 해당 값을 다운스트림에 전달하는 Subject이다. CurrentValueSubject과 달리 PassthroughSubject는 버퍼가 존재하지 않기 때문에 초기 값이 없다.
import Combine
let subject: PassthroughSubject<Int, Never> = .init()
let cancellables: AnyCancellable = subject.sink {
print($0)
}
subject.send(1)
subject.send(2)
subject.send(completion: .finished)
subject.send(3)
// 1
// 2
위의 코드와 같이 구독을 시작한 후 외부에서 전달한 값을 방출하는 것을 확인할 수 있다. 또한 완료 신호를 전달하면 구독이 취소되므로 이후에 전달한 값은 출력되지 않는 것을 확인할 수 있다.