Combine: Asynchronous Programming with Swift

08 October 2019 — Written by Paul Han
#swift5#Combine#

Combine은 Swift 생태계의 새로운 언어로써 도입되어 비동기 프로그래밍 세계의 혼란에 더 많은 질서를 가져오는 것을 목표로 합니다. 수년동안 Apple은 비동기 프로그래밍 문제를 몇가지 매커니즘(GCD, Notification, Delegate 등등)을 사용하여 개선해 왔습니다. 뿐만아니라 3rd-party 라이브러리에서도 이와 같은 문제들을 지원하고 있고 하나의 앱은 비동기 이벤트 처리를 하기위해 다양한 매커니즘을 사용해야 했는데 이러한 문제들을 종식시키기 위해 Combine의 API들을 Foundation, CoreData, SwiftUI 프레임워크 깊숙히 통합을 이루었습니다.

Publishers and Subscribers

Publishers and Subscribers

Publisher는 Value를 송출(publishes) 하고 Subscriber는 Publisher의 값을 받기 위해 수신(subscribes) 합니다.

import Foundation
import Combine

let publisher = Just(28)
let subscription = publisher
    .sink { value in
        print("Received value: \(value)")
    }
Received value: 28

sink Operator를 사용하여 송출된 값을 수신 할 수 있습니다.

Subjects

Subject는 Subscriber 이면서 동시에 Publisher 이며 값을 Subject를 통해 송출 가능하며 Publisher가 Subject를 구독하여 값을 전달(Relays) 가능 합니다.

import Foundation
import Combine

let subject = PassthroughSubject<String, Never>()

let subscription = subject
	.sink { value in
		print("Received value: \(value)")
}

subject.send("Hello!")

let publisher = Just("Here we go")
publisher.subscribe(subject)
Received value: Hello!
Received value: Here we go

Operators

연산자는 Publisher 인스턴스에 정의된 함수 입니다. 각 연산자는 새로운 Publisher를 반환하며 처리 단계를 추가하기 위하여 연산자를 연결할수 있습니다.

아래에서는 map 연산자가 새로운 Publisher를 반환하고 이를 각각 구독하여 출력되는 결과를 확인 할 수 있습니다.

다양한 연산자들은 Apple Publisher 문서를 참고하여 살펴 볼수 있습니다.

import Foundation
import Combine

let publisher1 = PassthroughSubject<Int, Never>()

let publisher2 = publisher1.map { value in
	value + 100
}

let subscription1 = publisher1
	.sink { value in
		print("Subscription1 received integer: \(value)")
}

let subscription2 = publisher2
	.sink { value in
		print("Subscription2 received integer: \(value)")
}

print("Publisher1 emits 28")
publisher1.send(28)

print("Publisher1 emits 50")
publisher1.send(50)
Publisher1 emits 28
Subscription1 received integer: 28
Subscription2 received integer: 128
Publisher1 emits 50
Subscription1 received integer: 50
Subscription2 received integer: 150

The rules of a subscription

구독자는 하나만 구독 가능 하며 0또는 그 이상의 값이 송출 될수 있으며 최대 하나의 완료만 존재 합니다 완료후에는 더 이상 아무것도 받을 수 없는 상태가 됩니다.

import Combine
import UIKit

enum ExampleError: Swift.Error {
    case somethingWentWrong
}

let subject = PassthroughSubject<String, ExampleError>()

// The handleEvents operator lets you intercept
// All stages of a subscription lifecycle
subject.handleEvents(receiveSubscription: { (subscription) in
        print("New subscription!")
    }, receiveOutput: { _ in
        print("Received new value!")
    }, receiveCompletion: { _ in
        print("A subscription completed")
    }, receiveCancel: {
        print("A subscription cancelled")
    })
    .replaceError(with: "Failure")
    .sink { (value) in
        print("Subscriber received value: \(value)")
    }

subject.send("Hello!")
subject.send("Hello again!")
subject.send("Hello for the last time!")
subject.send(completion: Subscribers.Completion<ExampleError>.failure(ExampleError.somethingWentWrong))
subject.send("Hello?? :(")
New subscription!
Received new value!
Subscriber received value: Hello!
Received new value!
Subscriber received value: Hello again!
Received new value!
Subscriber received value: Hello for the last time!
A subscription completed
Subscriber received value: Failure