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
간단한 앱
- 유저의 이름과 패스워드를 확인하는 과정이 있다.
- 이러한 과정은 메인 스레드를 차단하지 않고 사용자의 입력과 반응이 호환되어야 한다.
- 이미 많은 비동기 작업들이 일어 나고 있다.
- Target / Action
- 사용자의 입력에 대한 알림을 수신한다.
- Timer
- 사용자가 입력을 멈출 때까지 기다린다. → 서버에 네트워크 요청을 하지 않는다.
- KVO ( Key - Value Observing)
- 비동기 작업에 대한 진행률 업데이트
- Target / Action
- 입력을 하고나서 더 많은 비동기 작업을 수행한다.
- URLSession에 대한 요청 응답 대기
- 결과를 동기 검사 결과와 병합해야 한다. ( 비밀번호 )
- 모든 작업이 완료 되면 KVC( Key - Value Coding)와 같은 것을 사용하여 UI업데이트
- 비동기에 많은 요소들이 있는데 이것들을 같이 사용하는 경우 어려움을 느낄 수 있다.
- 그렇기 때문에 공통점을 찾게 되었다.
- 그래서 swift 전용 combine이 나오게 되었다.
Combine
- swift용으로 나왔기 때문에 Generic을 사용가능하다
- Combine은 type safe하다.
type safe???
타입에 안전하다는 의미, Int로 선언을 한 변수면 Int값만 들어가고 다른 값을 작성하는 경우 컴파일 타임에 오류가 발생한다.
- compostion First??
- 작은 연산자들을 결합하여 다양하게 만드는 것
- combine은 요청 기반이므로 앱의 메모리 사용량과 성능을 주의 깊게 관리가 가능해진다.
combine 구성요소
- Publishers: 값을 생산하고 호출
- Operators: 값을 변경하는 로직
- Subscribers: 값을 받기
Publishers
- publisher는 struct를 사용
- 여러 이벤트를 Subscriber에게 전송한다.
- value
- Successful Compltion
- Failure
protocol Publisher {
associatedtype Output
associatedtype Failure: Error
func subscribe<S: Subscriber>(_ subscriber: S)
where S.Input == Output, S.Failure == Failure
}
- Subscriber의 Input과 Publisher의 output이 동일
- Subscriber의 Failure과 Publisher의 Failure이 동일
Publisher의 예시
extension NotificationCenter {
struct Publisher: Combine.Publisher {
typealias Output = Notification
typealias Failure = Never
init(center: NotificationCenter,
name: Notification.Name,
object: Any? = nil)
}
}
Subscriber
- 참조 타입으로 class를 사용
protocol Subscriber {
associatedtype Input
associatedtype Failure: Error
func receive(subscription: Subscription)
func receive(_ input: Input) -> Subscribers.Demand
func receive(completion: Subscribers.Completion<Failure>)
}
- 3가지의 주요 기능이 존재
- 구독을 통해 publisher에서 전해준 데이터의 흐름을 제어하는 방법
- 입력 받기
- Publisher가 종료 되면 종료에 대한 이벤트 결과를 받을 수 있다
Subscription: publisher와 subscriber의 연결을 나타내는 프로토콜
Subscriber 예시
extension Subscribers {
class Assign<Root, Input>: Subscriber, Cancellable {
typealias Failure = Never
init(object: Root, keyPath: ReferenceWritableKeyPath<Root, Input>)
}
}
- swift에서 속성 값만 작성하는 경우 오류를 처리할 방법이 없기 때문에 Failure를 Never로 설정
사용예시
- subscriber를 할당
- publisher가 subscriber에게 subscription을 보냄
- demand를 통해 특정 개수 또는 무제한의 값 요청이 가능
- publisher가 해당 개수 이하의 값을 자유롭게 보냄
- publisher가 유한한 경우 완료 또는 실패를 보냄
one Subscription, zero or more values and a single Completion
1개의 구독, 0개 또는 많은 값과 단일 compleiton
- 학생들의 졸업에 대한 알림을 수신하고 싶다.
// Using Publisher and Subscriber
class Wizard {
var grade: Int
}
let merlin = Wizard(grade: 5)
let graduationPublisher =
NotificationCenter.Publisher(center: .default, name: .graduated, object: merlin)
let gradeSubscriber = Subscribers.Assign(object: merlin, keyPath: \.grade)
graduationPublisher.subscribe(gradeSubscriber)
- 다음과 같이 작성을 하면 컴파일이 안된다.
- 타입이 다르기 때문
- 중간에 과정을 통해 변환을 시켜주어야 한다. 이럴 때 사용하는게 Operator다.
Operator
- value Type이다
- Declarative Operator API ( 선언적 연산자 API )
- 많은 기능들이 존재한다.
업 스트림 (Upstream): publisher를 구독하는 것
다운 스트림 (Downstrem): subscriber에게 보내는 것
예시
extension Publishers {
struct Map<Upstream: Publisher, Output>: Publisher {
typealias Failure = Upstream.Failure
let upstream: Upstream
let transform: (Upstream.Output) -> Output
}
}
- 연결되는 업스트림과 업스트림의 출력을 자체 출력으로 변환하는 방법으로 초기화되는 구조체
- Map은 자체적으로 Failure를 생성하지 않기 때문에 업스트림의 Failure 유형을 통과 시킨다.
Map을 통해 직전에 컴파일이 불가능하던 코드를 변화 시켜보자
let graduationPublisher =
NotificationCenter.Publisher(center: .default, name: .graduated, object: merlin)
let gradeSubscriber = Subscribers.Assign(object: merlin, keyPath: \.grade)
let converter = Publishers.Map(upstream: graduationPublisher) { note in
return note.userInfo?["NewGrade"] as? Int ?? 0
}
converter.subscribe(gradeSubscriber)
- GraduationPublisher를 연결하고 해당 userInfo에 “NewGrade”가 정수로 변환이 되면 해당 정수를 반환하고 아니면 0을 반환한다.
// Operator Construction
extension Publisher {
func map<T>(_ transform: @escaping (Output) -> T) -> Publishers.Map<Self, T> {
return Publishers.Map(upstream: self, transform: transform)
}
}
모든 publisher가 사용이 가능하도록 프로토콜에 해당 기능 추가
실제 사용 예시를 보자
// Chained Publishers
let cancellable =
NotificationCenter.default.publisher(for: .graduated, object: merlin)
.map { note in
return note.userInfo?["NewGrade"] as? Int ?? 0
}
.assign(to: \.grade, on: merlin)
- assign은 취소 기능도 결합이 되어 있다.
- 취소를 사용하면 필요한 경우 게시자 및 구독자의 순서를 조기에 중단하는 것이 가능하다
Combine을 잘 사용하는 방법
- Compostion first이 기본 원칙이다.
- 작은 연산자들을 결합 하는 것
- 많은 정수를 동기로 표현하려면 정수 배열과 같은 것을 사용한다.
- 이러한 개념을 가져와 비동기 세계에 매핑했다
- 단일 값을 비동기로 표현해야 하는 경우, 나중에 발생하기 때문에 미래를 가지고 있다.
- 많은 값을 비동기적으로 표현해야 하는 경우는 Publisher다
// Chained Publishers
let cancellable =
NotificationCenter.default.publisher(for: .graduated, object: merlin)
.map { note in
return note.userInfo?["NewGrade"] as? Int ?? 0
}
.assign(to: \.grade, on: merlin)
- 기존의 코드는 잘못된 값이 그대로 저장이 되는데 이것을 방지해보자
let cancellable =
NotificationCenter.default.publisher(for: .graduated, object: merlin)
.compactMap { note in
return note.userInfo?["NewGrade"] as? Int
}
.assign(to: \.grade, on: merlin)
- compactMap의 클로저에서 nil을 반환하면 이를 필터링하여 스트림 아래로 더 이상 진행이 안된다.
// Composing Operators
let cancellable =
NotificationCenter.default.publisher(for: .graduated, object: merlin)
.compactMap { note in
return note.userInfo?["NewGrade"] as? Int
}
.filter { $0 >= 5 }
.prefix(3)
.assign(to: \.grade, on: merlin)
- int로 변환이 가능하고, 5학년이상이며, 3번의 졸업만 가능하도록
map과 filter모두 좋은 연산자이지만 주로 동기적 상황에서 사용한다.
Zip
- zip은 업스트림의 입력을 단일 튜플로 변환한다.
- 그렇기 때문에 3가지 비동기 작업의 결과를 기다리기 위해 zip을 사용한다.
Zip3(organizing, decomposing, arranging)
.map { $0 && $1 && $2 }
.assign(to: \.isEnabled, on: continueButton)
Combine Latest
- zip과 마찬가지로 여러 upstream을 단일로 변경한다.
- 하지만 zip과 달리 진행하려면 업스트림의 입력이 필요해 일종의 when/or 작업이 된다.
- 업스트림이 변경이 되면 새로운 이벤트가 발생
- 그렇기 때문에 3개의 업스트림을 단일 bool로 변경하여 이를 isEnabled에 기록한다.
- 1개라도 거짓이면 거짓이다
combine 사용예시
- 알림 센터 사용하고 알림을 받은 다음, 내용을 살펴보고 조치를 취할지 여부를 결정하는 경우 filter를 사용
- 여러 비동기 작업의 결과에 가중치를 부여하는 경우 네트워크 작업을 포함하여 Zip을 사용
- URLSession을 사용하여 데이터를 받아온 경우 decoding할 때 decode라는 연산자
'Combine' 카테고리의 다른 글
[WWDC 19] Combine in Practice (0) | 2024.07.08 |
---|