이번 포스트에서는 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
위의 코드와 같이 구독을 시작한 후 외부에서 전달한 값을 방출하는 것을 확인할 수 있다. 또한 완료 신호를 전달하면 구독이 취소되므로 이후에 전달한 값은 출력되지 않는 것을 확인할 수 있다.