[Swift/Combine] Combine Framework

이정훈·2023년 2월 16일
0

Combine Framework

목록 보기
1/4
post-thumbnail

iOS 개발자 모집 공고를 둘러보다 보면 여러가지 역량들을 요구한다. 예를 들자면..

🙋 iOS 어플리케이션을 개발 해보신분
🙋 Swift 개발이 익숙하신분
🙋 Swift UIKit 개발이 익숙하신분
등등

그리고 대부분의 모집 공고에서 꼬리표 처럼 따라 다니는 그것은 바로..
🙋 RxSwift 사용 가능하신분 or 🙋 Combine 사용 가능하신분

Swift에서 비동기 이벤트를 핸들링 하기 위해 마이크로소프트에서 개발한 외부 라이브러리인 RxSwift를 사용할 수 있다.

Combine?


Apple에서는 이러한 외부 라이브러리를 가져다 쓰는 것이 불편(?)했는지 2019년 iOS 13 버전부터 사용 가능한 Combine Framework를 공개했다.

다시말해, Combine은 비동기 이벤트를 핸들링 할 수 있게하는 Apple의 순정 Framework이다.

지금부터 Combine Framework의 사용법을 몇 차례 포스트를 통해 알아보려고 한다.

Publisher와 Subscriber


Combine Framework를 사용하기 위해서는 Publisher와 Subscriber로 데이터를 주고 받는 것에 대한 이해에서 부터 출발한다.

먼저 Publisher는 이벤트(혹은 값)를 전달하는 존재로 생각할 수 있고 Subscriber는 Publisher가 내보낸 이벤트(혹은 값)를 수신하는 존재로 생각하면 된다.

WWDC 2019에서 Apple은 Combine framework의 흐름을 소개할때 다음과 같이 소개한다.

먼저 Subscriber가 Publisher를 subscribe하기 전까지 Publisher는 대기 상태로 존재한다.(여기서 Publisher는 Publisher protocol을 준수하는 인스턴스라고 하겠다.)

  1. Pusblisher의 subscribe(_:) 메서드의 전달인자로 Subscriber protocol을 준수하는 인스턴스를 전달하여 Pubulisher와 Subscriber를 연결한다.

  2. Subscriber의 receive(subscription:) 메서드는 subscribe 요청을 인지하고 Publisher로 부터 Subscription 인스턴스를 전달 받는다.(Publisher-Subscriber 연결 완료)

  3. Subscriber는 전달 받은 Subscription의 request(_:) 메서드를 통해 Publisher의 값을 전달하라고 요청한다.
    그럼 비로소 Publisher는 Subscriber에게 값과 오류 타입을 함께 전달한다(오류가 발생하지 않으면 Never 오류 타입을 전달한다.)

  4. Subscriber의 receive(_:) 메서드는 Publisher로 부터 전달 받은 값을 처리한다.

  5. 만약 더 이상 Subscriber에게 전달할 값이 없으면 receive(completion:) 메서드를 통해 completion, 혹은 오류를 전달 받는다.

위의 과정에 사용된 메서드들은 Publisher의 protocol과 Subscriber의 protocol에 선언 되어 있다.

📤 Publisher protocol


public protocol Publisher<Output, Failure> {

    /// The kind of values published by this publisher.
    associatedtype Output

    /// The kind of errors this publisher might publish.
    ///
    /// Use `Never` if this `Publisher` does not publish errors.
    associatedtype Failure : Error

    /// Attaches the specified subscriber to this publisher.
    ///
    /// Implementations of ``Publisher`` must implement this method.
    ///
    /// The provided implementation of ``Publisher/subscribe(_:)-4u8kn``calls this method.
    ///
    /// - Parameter subscriber: The subscriber to attach to this ``Publisher``, after which it can receive values.
    func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}

Swift 문법 짚고 넘어가기

protocol에는 일반적으로 함수에서 Generic을 선언 하듯이 Generic을 선언할 수 없다.
대신 protocol에서 Generic처럼 동적으로 타입을 지정할 수 있는 associatedtype placeholder가 있다.

추가 설명

  • Output 타입은 Publisher가 Subscriber에게 전달하는 값의 타입을 의미한다.

  • Failuer Publisher가 Subscriber에게 전달할 오류 타입이다. Publisher가 Subscriber에게 전달할때 오류가 발생하지 않으면 Never로 지정한다.

  • receive(subscriber:) 메서드는 Subscriber protocol을 준수하는 인스턴스를 전달인자로 전달 받는다.

⚠️ Apple 문서에도 receive(subscriber:) 메서드 대신 위의 이미지에서 설명된 subscribe(_:) 메서드를 사용할 것을 권하고 있다.

📥 Subscriber protocol


public protocol Subscriber<Input, Failure> : CustomCombineIdentifierConvertible {

    /// The kind of values this subscriber receives.
    associatedtype Input

    /// The kind of errors this subscriber might receive.
    ///
    /// Use `Never` if this `Subscriber` cannot receive errors.
    associatedtype Failure : Error

    /// Tells the subscriber that it has successfully subscribed to the publisher and may request items.
    ///
    /// Use the received ``Subscription`` to request items from the publisher.
    /// - Parameter subscription: A subscription that represents the connection between publisher and subscriber.
    func receive(subscription: Subscription)

    /// Tells the subscriber that the publisher has produced an element.
    ///
    /// - Parameter input: The published element.
    /// - Returns: A `Subscribers.Demand` instance indicating how many more elements the subscriber expects to receive.
    func receive(_ input: Self.Input) -> Subscribers.Demand

    /// Tells the subscriber that the publisher has completed publishing, either normally or with an error.
    ///
    /// - Parameter completion: A ``Subscribers/Completion`` case indicating whether publishing completed normally or with an error.
    func receive(completion: Subscribers.Completion<Self.Failure>)
}

추가 설명

  • Input은 Publisher로 부터 전달 받은 값의 타입을 의미한다.

  • Failure는 마찬가지로 오류 타입을 의미, 마찬가지로 오류를 전달 받지 않으면 Never 타입을 사용한다.

  • receive(subscription:) 메서드는 Publisher로 부터 Subscription을 전달 받는다.

  • receive(_ input:) 메서드는 Publisher로 부터 값을 전달 받는다. 반환 값은 앞으로
    Publisher로 부터 얼마나 요소를 전달 받을 수 있는지를 나타낸다.

  • receive(completion:_) Publisher로 부터 completion 혹은 오류를 전달 받는다.

구현


위의 두 protocol을 채택한 class나 구조체로 Publisher와 Subscriber를 직접 정의할 수 있지만 예를 들어 Sequence 타입의 경우 Publisher를 반환하는 publisher property를 이용하면 쉽게 Publisher를 생성할 수 있다.(Sequence 타입에 정의 되어 있는 property이지만 Combine framework의 extension으로 정의 되어 있기 때문에 반드시 Combine을 import 해주어야 한다.)

import Combine

var numbers = [1, 2, 3, 4, 5]

//MARK: - Publisher
let pub = numbers.publisher    //publisher 생성

위의 코드와 같이 [Int] 타입에 publisher 프로퍼티를 사용하면 Output이 Int 타입이고 Failure 타입이 Never인 Publisher를 반환한다.

Subscriber 또한 두 가지 메서드를 통해 쉽게 Publisher를 subscribe 할 수 있는데

  • sink(receiveCompletion:receiveValue:)
  • assign(to:on:)

두 메서드를 사용하면 Publisher와 동일한 Input, Failure 타입을 가지는 Subscriber를 생성할 수 있다.

//MARK: - Subscriber
//sink로 publisher subscribe
let sub = pub.sink(receiveCompletion: {
    print("received completion: \($0)")
}, receiveValue: {
    print("received value: \($0)")
})

위의 예시의 경우 Publisher로 부터 sink 메서드를 사용하여 Subscriber를 생성하였으며, receiveCompletion parameter를 통해 Publisher로 부터 completion을 받았을 때 수행할 작업을 클로저로, receiveValue parameter를 통해 Publisher로 부터 값을 전달 받았을때 수행할 작업을 클로저로 전달해 주기만 하면 된다.

위의 두 Publisher와 Subscriber의 실행 결과는 다음과 같다.

received value: 1
received value: 2
received value: 3
received value: 4
received value: 5
received completion: finished

이번 포스트에서는 Publisher와 Subscriber의 정의와 데이터가 전달 되는 흐름에 대하여 알아 보았다.

이번 포스트의 예시 코드에서 Publisher는 Subscriber에게 즉각적으로 데이터를 전달하고 있다. 하지만 이벤트가 발생할 때를 인지하여 Publisher가 Subscriber에게 값을 전달하도록 하려면..?

다음 포스트에서는 이벤트(데이터의 변화)가 발생할때 어떻게 Publisher가 이벤트를 인식하고 Subscriber에게 이벤트를 전달할 수 있을지 알아보려고 한다.

profile
새롭게 알게된 것을 기록하는 공간

0개의 댓글