Combine
Combine은 시간 경과에 따른 값을 처리하기 위한 통합적이고 선언적인 API입니다.
Cocoa SDK에는 여러 가지 비동기 인터페이스가 존재합니다. 하지만 이것들을 함께 사용할 때 어려움을 겪게 됩니다. Combine을 통해 여러 가지 비동기 인터페이스들을 함께 사용하여 어려움을 해결할 수 있습니다.
여러 가지 비동기 인터페이스
- Target/Action
- Notification center
- URLSession
- Key-value observing
- Ad-hoc callbacks
Combine Features
Combine은 Swift로 작성되었으며 Swift를 위해 작성되었습니다. 즉, Generics과 같은 Swift 기능을 활용할 수 있습니다.
- Generic: Generics를 사용하면 작성해야 하는 boilerplate 코드의 양을 줄일 수 있습니다. 또한 비동기 동작에 대한 알고리즘을 한 번 작성하면 모든 종류의 비동기 인터페이스에 적용할 수 있습니다.
- Type Safe: Combine은 Type Safe하기 때문에 컴파일 타임에 오류를 잡을 수 있습니다.
- Composition First: Combine의 핵심 개념은 간단하고 이해하기 쉽지만, 이를 합치면 부분의 합보다 더 큰 것을 만들 수 있습니다. 즉, 요소들을 조합해 더 복잡한 무언가를 쉽게 만들 수 있게 디자인되었습니다.
- Request driven: Combine은 요청을 기반으로 하기 때문에 앱의 메모리 사용과 성능을 보다 신중하게 관리할 수 있습니다.
Key Concepts
Combine은 크게 세 가지로 이루어져 있습니다.
- Publishers
- Subscribers
- Operators
Publisher
Combine의 선언적 부분입니다. 값과 오류가 어떻게 생성되는지 정의합니다.
Publisher Protocol
- Output: 생성되는 값의 종류
- Failure: 생성되는 에러의 종류
- subscribe(:_): Publisher의 핵심 기능이며, Output과 Subscriber의 Input이 일치해야 하고 Publisher와 Subscriber의 Failure이 일치해야 한다.
Subscriber
Publisher에 대응하는 값을 수신하면 동작하고 상태를 변경하는 역할을 합니다.
Publisher가 유한한 경우 Completion을 포함한 값을 수신합니다.
Subscriber Protocol
- Input
- Failure
- receive(subscription:_): 구독권을 수신하는 메소드로, 구독권은 Subscriber가 Publisher에서 수신자로의 데이터 흐름을 제어할 수 있다.
- receive(input:_): 입력값을 받아올 수 있다.
- receive(completion:_): Publisher가 유한한 경우 완료 또는 실패 중 하나가 될 수 있는 completion 값을 받아올 수 있다.
Publisher와 Subscriber의 패턴
- Subscriber를 보관하는 컨트롤러 객체가 Subscriber를 Publisher에 연결하여 Subscribe를 호출합니다.
- Publisher는 Subscriber에게 Subscription을 보냅니다. Subscriber는 이를 사용하여 Publisher에게 특정 수의 값 또는 무제한의 값을 요청합니다. (Demand)
- Publisher가 Subscriber에게 요청 받은 값 또는 그 이하의 값을 전달합니다.
- Publisher가 유한한 경우 completion 또는 Error를 보냅니다.
Operator
Operator는 Publisher protocol을 채택하며, 선언적이므로 값 타입입니다.
Operator는 값을 변경하거나 추가하고 제거하는 등 여러 가지 다른 종류의 동작을 설명합니다. 또한 upstream의 다른 Publisher에 구독을 하고, 그 결과를 downstream의 Subscriber에게 전달합니다.
즉, 연산자를 이용하여 데이터 스트림의 값을 변환하고, 여러 스트림을 조합하는 등 원하는 형태로 가공할 수 있습니다.
Map
map은 Operator의 대표적인 예입니다.
map은 어떤 upstream에 연결할 것인지, 그리고 upstream의 결과를 어떻게 변환할 것인지 초기화하는 구조체입니다.
또한 자체적으로 Failure를 생성하지 않기 때문에 upstream의 Failure 타입을 미러링하여 전달합니다.
extension Publisher에서 Operator의 이름을 딴 함수를 사용하여 fluent한 구문을 작성할 수 있습니다.
인수로는 upstream을 제외하고 map을 초기화하는 데 필요한 모든 것입니다. Publisher의 확장 기능으로 self를 사용할 수 있기 때문입니다.
map 예제
아래 예제는 컴파일 에러를 발생시키고 있습니다.
- wizard 클래스에서 NotificationCenter.Publisher를 생성했습니다.
- 다음으로 Assign Subscriber를 만들어 Merlin의 grade 속성을 새 grade를 쓰도록 만들었습니다.
- Subscribe를 호출하여 Publisher와 Subscriber를 연결하려고 했지만, 여기서 Publisher와 Subscriber의 타입이 일치하지 않아 컴파일 에러가 발생했습니다.
따라서 중간에서 타입을 변환해주는 것이 필요한데, 이것이 Operator이며 map을 통해 해결해보겠습니다.
- map을 통해 converter를 추가했습니다.
- upstream의 값을 받아와 클로저 내에서 변환을 수행하고, 이는 Subscriber에 연결할 수 있습니다.
Assign은 cancelable을 반환합니다. cancellation은 필요한 경우 Publisher와 Subscriber의 구독을 일찍 해제할 수 있습니다.
fluent한 map 구문으로 작성하기
CompactMap
클로저 내에서 nil을 반환하면 compactMap이 필터링하여 스트림이 더 이상 진행하지 않도록 하는 연산자입니다.
Filter
정의되어 있는 클로저의 조건과 일치하는 값만 반환합니다.
Combining Publishers
Map과 Filter는 주로 동기적 동작을 위해서 사용됩니다. 비동기적 동작을 위한 연산자가 두 가지 존재합니다.
- Zip
- CombineLatest
Zip
Zip은 여러 개의 upstream의 입력 값을 단일 튜플로 변환합니다. (When/And)
아래 예제는 세 개의 비동기 작업 결과를 위해 세 개의 upstream이 Zip을 사용합니다. 각각 Boolean 결과를 제공합니다.
튜플을 단일 Boolean으로 매핑하고 버튼의 isEnabled의 속성에 사용합니다.
CombineLatest
CombineLatest는 Zip과 마찬가지로 여러 개의 upstream 입력 값을 통해 단일 값으로 변환합니다.
하지만 Zip과는 다르게 upstream에서 변경사항이 발생하면 새로운 값을 결합하여 downstream으로 전달합니다. (When/Or)
아래 예제는 세 개의 upstream을 가져오고, 세 스위치의 boolean 상태가 변경될 때 이를 다시 단일 boolean 값으로 변환한 후 downstream의 isEnabled 속성에 사용합니다.
Declarative Operator API
연산자를 fluent한 구문으로 사용하는 방법은 각각의 연산자를 체인으로 연결하고 각 단계별로 작업을 수행하여 마지막 연산자의 값을 반환합니다.
이를 Declarative Operator API라고 합니다.
- Functional transformations
- List operations
- Error handling
- Thread or queue movement
- Scheduling and time
그리고 많은 연산자를 사용할 수 있기 때문에 복합할 수 있습니다. 애플은 Combine에 대한 핵심 디자인 원칙인 Composition으로 돌아가는 것을 권장합니다.
Try composition first
많은 작업을 수행하는 소수의 연산자를 제공하는 대신, 각각의 작은 작업을 수행하는 많은 연산자를 제공하여 이해하기 쉽게 만들었습니다.
연산자를 쉽게 탐색할 수 있도록 기존 Swift Collection API에서 연산자 이름에 대한 영감을 얻었습니다.
WWDC
https://developer.apple.com/videos/play/wwdc2019/722/
Introducing Combine - WWDC19 - Videos - Apple Developer
Combine is a unified declarative framework for processing values over time. Learn how it can simplify asynchronous code like networking,...
developer.apple.com