- Combine의 Publisher와 Subscriber에 대해 알아보겠다.
- Xcode의 Playground에서 실습을 진행하려고 한다.
실습 준비
- Playground에서의 실습을 보다 편하게 진행하기 위해
- 다음과 같은 코드를 Playground의 Sources 폴더 안에 임의의 파일에 작성해두자.
import Foundation
public func example(of description: String, action: () -> Void) {
print("\n——— Example of:", description, "———")
action()
}
Publisher
- Playground 최상단에 Foundation과 Combine을 import 한다.
import Foundation
import Combine
- 우선은 Publisher와 비슷한 Notification을 통해 코드를 구현해보자.
example(of: "Publisher") {
let myNotification = Notification.Name("MyNotification")
let center = NotificationCenter.default
let observer = center.addObserver(forName: myNotification, object: nil, queue: nil) { (notification) in
print("Notification received!")
}
center.post(name: myNotification, object: nil)
center.removeObserver(observer)
}
- 이것이 Publisher로부터 온 출력 값이라고 할 수는 없지만
- 실행해보면 다음과 같이 출력된다.
- 실제로 Publisher로부터 값을 받으려면 Subscriber가 필요하다.
——— Example of: Publisher ———
Notification received!
Subscriber
- NotificationCenter의 publisher를 sink로 subscribe하는 것을 구현해보자.
example(of: "Subscriber") {
let myNotification = Notification.Name("MyNotification")
let publisher = NotificationCenter.default.publisher(for: myNotification, object: nil)
let center = NotificationCenter.default
let subscription = publisher.sink { _ in
print("Notification received from a publisher!")
}
center.post(name: myNotification, object: nil)
subscription.cancel()
}
——— Example of: Subscriber ———
Notification received from a publisher!
- 이제 Just라는 새로운 예시를 보면, Just를 통해 새로운 Publisher를 생성한다.
- sink를 통해 publisher를 subscribe 해서 subscription을 생성한다.
- 그리고 event를 전달 받을 때마다 메세지를 print한다.
example(of: "Just") {
let just = Just("Hello world!")
_ = just
.sink(
receiveCompletion: {
print("Received completion", $0)
},
receiveValue: {
print("Received value", $0)
}
)
}
——— Example of: Just ———
Received value Hello world!
Received completion finished
- 위 코드에다 추가적인 subscriber를 통해 한번 더 subscribe 해보자.
_ = just
.sink(
receiveCompletion: {
print("Received completion (another)", $0)
},
receiveValue: {
print("Received value (another)", $0)
}
)
- 한번 더 subscription 과정이 이뤄지고, 다음과 같이 출력된다.
——— Example of: Just ———
Received value Hello world!
Received completion finished
Received value (another) Hello world!
Received completion (another) finished
- Combine의 또 다른 built-in subscriber인 assign도 살짝 알아보자.
assign(to:on:)
을 다음과 같이 구현해보자.- didSet이 있는 프로퍼티를 갖고 있는 class를 정의하고 instance를 생성한다.
- string array로부터 publisher를 생성한다.
- publisher를 subscribe 한다.
- 생성한 object의 property에 전달 받은 값을 할당한다.
example(of: "assign(to:on:)") {
class SomeObject {
var value: String = "" {
didSet {
print(value)
}
}
}
let object = SomeObject()
let publisher = ["Hello", "world!"].publisher
_ = publisher
.assign(to: \.value, on: object)
}
——— Example of: assign(to:on:) ——
Hello
world!
Cancellable
- Subscriber가 Publisher로부터 더 이상 값을 전달 받고 싶지 않을 때,
- resource를 낭비하지 않기 위해 Subscription을 cancel 하는 것이 좋다.
- Subscriber는 AnyCancellable instance를 리턴하고,
- AnyCancellable은 Cancellable 프로토콜을 따라서,
cancel()
을 사용하면 된다.
subscription.cancel()
커스텀 Subscriber
- 다음과 같이 커스텀 IntSubscriber를 만들어보자.
- 우선, 1부터 6까지의 범위 publisher를 만든다.
- 커스텀 IntSubscriber를 선언하고, Input과 Failure를 명시한다.
- 3가지 종류의 필수 메소드, receive를 구현한다.
- publisher에게 불리는
receive(subscription:)
를 .request로 구현한다. - 각 값을 print 하고, .max(0)과 동일한 .none을 return 한다.
- completion event를 print 한다.
example(of: "Custom Subscriber") {
let publisher = (1...6).publisher
final class IntSubscriber: Subscriber {
typealias Input = Int
typealias Failure = Never
func receive(subscription: Subscription) {
subscription.request(.max(3))
}
func receive(_ input: Int) -> Subscribers.Demand {
print("Received value", input)
return .none
}
func receive(completion: Subscribers.Completion<Never>) {
print("Received completion", completion)
}
}
let subscriber = IntSubscriber()
publisher.subscribe(subscriber)
}
——— Example of: Custom Subscriber ———
Received value 1
Received value 2
Received value 3
Future
- Just와 비슷하게 Future은 비동기 방식으로 하나의 결과를 emit하고 종료한다.
- Future를 아래 코드와 같이 사용해보자.
Future<Int, Never>
안의 코드는 promise를 생성하고,- 일정 시간의 delay 후 integer를 증가시킨다.
- Future은 결국 하나의 값을 emit하고 종료되거나, fail 하는 publisher이다.
- promise는 typealias로
(Result<Output, Failure>) -> Void
이렇게 생겼다. - 그 밑의 코드는 integer 1을 3초의 delay 후 증가시키고,
- sink subscriber를 통해 value와 completion을 받아오는 것이다.
- store에 대해서는 나중에 알아 볼 예정이다.
example(of: "Future") {
func futureIncrement(integer: Int, afterDelay delay: TimeInterval) -> Future<Int, Never> {
Future<Int, Never> { promise in
DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
promise(.success(integer + 1))
}
}
}
let future = futureIncrement(integer: 1, afterDelay: 3)
future
.sink(receiveCompletion: { print($0) },
receiveValue: { print($0) })
.store(in: &subscriptions)
}
——— Example of: Future ———
2
finished