본문 바로가기

swift

[Swift] Combine

 

1. Combine 공식문서

  1. Customize handling of asynchronous events by combining event-processing operators.
    이벤트 처리 연산자를 결합하여 비동기 이벤트를 사용자 지정 방식으로 처리하세요.
  2. iOS 13.0+
    iOS 13.0 이상
  3. iPadOS 13.0+
    iPadOS 13.0 이상

  1. Overview
    개요
  2. The Combine framework provides a declarative Swift API for processing values over time.
    Combine 프레임워크는 시간에 따라 변하는 값을 처리하기 위한 선언형 Swift API를 제공합니다.
  3. These values can represent many kinds of asynchronous events.
    이러한 값들은 여러 종류의 비동기 이벤트를 나타낼 수 있습니다.
  4. Combine declares publishers to expose values that can change over time, and subscribers to receive those values from the publishers.
    Combine은 시간이 지나면서 변경될 수 있는 값을 노출하는 퍼블리셔(Publisher)와, 그 값을 수신하는 구독자(Subscriber)를 선언합니다.
  5. The Publisher protocol declares a type that can deliver a sequence of values over time.
    Publisher 프로토콜은 시간이 지나면서 값의 시퀀스를 전달할 수 있는 타입을 선언합니다.
  6. Publishers have operators to act on the values received from upstream publishers and republish them.
    퍼블리셔는 업스트림 퍼블리셔에서 받은 값에 대해 연산을 수행하고, 이를 다시 퍼블리셔 형태로 전달하는 연산자(Operators)를 제공합니다.
  7. At the end of a chain of publishers, a Subscriber acts on elements as it receives them.
    퍼블리셔 체인의 끝에서 구독자(Subscriber)는 받은 요소에 대해 특정 동작을 수행합니다.
  8. Publishers only emit values when explicitly requested to do so by subscribers.
    퍼블리셔는 구독자로부터 명시적인 요청이 있을 때만 값을 방출합니다.
  9. This puts your subscriber code in control of how fast it receives events from the publishers it’s connected to.
    이를 통해 구독자 코드가 퍼블리셔로부터 이벤트를 받는 속도를 직접 제어할 수 있습니다.
  10. Several Foundation types expose their functionality through publishers, including Timer, NotificationCenter, and URLSession.
    Timer, NotificationCenter, URLSession 등 여러 Foundation 타입이 퍼블리셔를 통해 기능을 제공합니다.
  11. Combine also provides a built-in publisher for any property that’s compliant with Key-Value Observing.
    Combine은 Key-Value Observing(KVO)을 준수하는 모든 프로퍼티에 대해 기본 퍼블리셔를 제공합니다.
  12. You can combine the output of multiple publishers and coordinate their interaction.
    여러 퍼블리셔의 출력을 결합하고, 그들의 상호작용을 조정할 수 있습니다.
  13. For example, you can subscribe to updates from a text field’s publisher, and use the text to perform URL requests.
    예를 들어, 텍스트 필드의 퍼블리셔에서 업데이트를 구독하고 입력된 텍스트를 사용하여 URL 요청을 수행할 수 있습니다.
  14. You can then use another publisher to process the responses and use them to update your app.
    그런 다음, 다른 퍼블리셔를 사용하여 응답을 처리하고 이를 앱을 업데이트하는 데 활용할 수 있습니다.
  15. By adopting Combine, you’ll make your code easier to read and maintain,
    Combine을 도입하면 코드가 더 읽기 쉽고 유지보수가 용이해집니다.
  16. by centralizing your event-processing code and eliminating troublesome techniques like nested closures and convention-based callbacks.
    이벤트 처리 코드를 중앙에서 관리하고, 중첩된 클로저나 관습적인 콜백 같은 복잡한 기법을 제거할 수 있습니다.
 
 
 

2. SubScriber 공식문서

  1. Overview
    개요
  2. A Subscriber instance receives a stream of elements from a Publisher, along with life cycle events describing changes to their relationship.
    Subscriber 인스턴스는 Publisher로부터 요소의 스트림을 수신하며, 관계의 변화를 설명하는 라이프 사이클 이벤트도 함께 받습니다.
  3. A given subscriber’s Input and Failure associated types must match the Output and Failure of its corresponding publisher.
    특정 Subscriber의 Input 및 Failure 연관 타입은 해당 Publisher의 Output 및 Failure 타입과 일치해야 합니다.
  4. You connect a subscriber to a publisher by calling the publisher’s subscribe(_:) method.
    Publisher의 subscribe(_:) 메서드를 호출하여 Subscriber를 Publisher에 연결할 수 있습니다.
  5. After making this call, the publisher invokes the subscriber’s receive(subscription:) method.
    이 호출이 이루어지면, Publisher는 Subscriber의 receive(subscription:) 메서드를 호출합니다.
  6. This gives the subscriber a Subscription instance, which it uses to demand elements from the publisher, and to optionally cancel the subscription.
    이를 통해 Subscriber는 Subscription 인스턴스를 받으며, 이를 사용하여 Publisher에 요소를 요청하거나 구독을 취소할 수 있습니다.
  7. After the subscriber makes an initial demand, the publisher calls receive(_:), possibly asynchronously, to deliver newly-published elements.
    Subscriber가 초기 요청을 수행한 후, Publisher는 receive(_:) 메서드를 호출하여 새로운 요소를 전달합니다. (비동기적으로 호출될 수도 있음)
  8. If the publisher stops publishing, it calls receive(completion:), using a parameter of type Subscribers.Completion to indicate whether publishing completes normally or with an error.
    Publisher가 발행을 중단하면, receive(completion:) 메서드를 호출하며, Subscribers.Completion 타입의 매개변수를 사용하여 정상 종료인지 오류 발생인지 나타냅니다.
  9. Combine provides the following subscribers as operators on the Publisher type:
    Combine은 Publisher 타입에서 사용할 수 있는 다음과 같은 Subscriber 연산자를 제공합니다.
  10. sink(receiveCompletion:receiveValue:) executes arbitrary closures when it receives a completion signal and each time it receives a new element.
    sink(receiveCompletion:receiveValue:)는 완료 신호를 받을 때 및 새로운 요소를 받을 때마다 임의의 클로저를 실행합니다.
  11. assign(to:on:) writes each newly-received value to a property identified by a key path on a given instance.
    assign(to:on:)는 새로 수신된 값을 지정된 인스턴스의 키 경로(KeyPath)로 식별된 속성에 할당합니다.
 
 
 

3. Publisher

1. 공식 문서

  1. Overview
    개요
  2. A publisher delivers elements to one or more Subscriber instances.
    Publisher는 하나 이상의 Subscriber 인스턴스에 요소를 전달합니다.
  3. The subscriber’s Input and Failure associated types must match the Output and Failure types declared by the publisher.
    Subscriber의 Input 및 Failure 연관 타입은 Publisher가 선언한 Output 및 Failure 타입과 일치해야 합니다.
  4. The publisher implements the receive(subscriber:) method to accept a subscriber.
    Publisher는 receive(subscriber:) 메서드를 구현하여 Subscriber를 받을 수 있도록 합니다.

  1. After this, the publisher can call the following methods on the subscriber:
    이후 Publisher는 Subscriber에서 다음 메서드를 호출할 수 있습니다.
  2. receive(subscription:): Acknowledges the subscribe request and returns a Subscription instance.
    receive(subscription:) : 구독 요청을 확인하고 Subscription 인스턴스를 반환합니다.
  3. The subscriber uses the subscription to demand elements from the publisher and can use it to cancel publishing.
    Subscriber는 Subscription을 사용하여 Publisher에서 요소를 요청할 수 있으며, 필요하면 구독을 취소할 수도 있습니다.
  4. receive(_:): Delivers one element from the publisher to the subscriber.
    receive(_:) : Publisher가 Subscriber에 하나의 요소를 전달합니다.
  5. receive(completion:): Informs the subscriber that publishing has ended, either normally or with an error.
    receive(completion:) : Publisher가 정상 종료되었거나 오류가 발생했음을 Subscriber에 알립니다.

  1. Every Publisher must adhere to this contract for downstream subscribers to function correctly.
    모든 Publisher는 하위 Subscriber가 올바르게 동작하도록 이 규칙을 따라야 합니다.
  2. Extensions on Publisher define a wide variety of operators that you compose to create sophisticated event-processing chains.
    Publisher에 대한 확장을 통해 다양한 연산자를 사용할 수 있으며, 이를 조합하여 복잡한 이벤트 처리 체인을 만들 수 있습니다.
  3. Each operator returns a type that implements the Publisher protocol.
    각 연산자는 Publisher 프로토콜을 구현하는 타입을 반환합니다.
  4. Most of these types exist as extensions on the Publishers enumeration.
    이러한 타입의 대부분은 Publishers 열거형의 확장으로 존재합니다.
  5. For example, the map(_:) operator returns an instance of Publishers.Map.
    예를 들어, map(_:) 연산자는 Publishers.Map 인스턴스를 반환합니다.

  1. Tip
  2. A Combine publisher fills a role similar to, but distinct from, the AsyncSequence in the Swift standard library.
    Combine의 Publisher는 Swift 표준 라이브러리의 AsyncSequence와 유사한 역할을 하지만, 서로 다른 방식으로 동작합니다.
  3. A Publisher and an AsyncSequence both produce elements over time.
    Publisher와 AsyncSequence는 모두 시간이 지나면서 요소를 생성합니다.
  4. However, the pull model in Combine uses a Subscriber to request elements from a publisher,
    하지만 Combine의 "풀 모델(Pull Model)" 은 Subscriber가 Publisher에서 요소를 요청하는 방식으로 동작합니다.
  5. while Swift concurrency uses the for-await-in syntax to iterate over elements published by an AsyncSequence.
    반면, Swift의 동시성(Concurrency)에서는 for-await-in 구문을 사용하여 AsyncSequence에서 발행된 요소를 순회합니다.
  6. Both APIs offer methods to modify the sequence by mapping or filtering elements,
    두 API 모두 요소를 매핑하거나 필터링하는 방법을 제공합니다.
  7. while only Combine provides time-based operations like debounce(for:scheduler:options:) and throttle(for:scheduler:latest:),
    하지만 Combine만이 debounce(for:scheduler:options:) 및 throttle(for:scheduler:latest:) 같은 시간 기반 연산을 제공합니다.
  8. and combining operations like merge(with:) and combineLatest(::).
    또한, merge(with:) 및 combineLatest(_:_:). 같은 퍼블리셔 결합 연산도 제공합니다.
  9. To bridge the two approaches, the property values exposes a publisher’s elements as an AsyncSequence,
    이 두 가지 접근 방식을 연결하기 위해, values 속성을 사용하면 Publisher의 요소를 AsyncSequence로 변환할 수 있습니다.
  10. allowing you to iterate over them with for-await-in rather than attaching a Subscriber.
    이를 통해 Subscriber를 붙이는 대신, for-await-in 구문을 사용하여 요소를 순회할 수 있습니다.

  1. Creating Your Own Publishers
    직접 Publisher 생성하기
  2. Rather than implementing the Publisher protocol yourself,
    Publisher 프로토콜을 직접 구현하는 대신,
  3. you can create your own publisher by using one of several types provided by the Combine framework:
    Combine 프레임워크에서 제공하는 여러 유형을 사용하여 직접 Publisher를 만들 수 있습니다.
  4. Use a concrete subclass of Subject, such as PassthroughSubject, to publish values on-demand by calling its send(_:) method.
    PassthroughSubject 같은 Subject의 구체적인 서브클래스를 사용하여 send(_:) 메서드를 호출해 값을 원하는 시점에 발행할 수 있습니다.
  5. Use a CurrentValueSubject to publish whenever you update the subject’s underlying value.
    CurrentValueSubject 를 사용하면 해당 값이 변경될 때마다 자동으로 값을 발행할 수 있습니다.
  6. Add the @Published annotation to a property of one of your own types.
    사용자 정의 타입의 속성에 @Published 어노테이션을 추가할 수도 있습니다.
  7. In doing so, the property gains a publisher that emits an event whenever the property’s value changes.
    이렇게 하면 해당 속성은 값이 변경될 때마다 이벤트를 방출하는 Publisher가 됩니다.
  8. See the Published type for an example of this approach.
    이 방법의 예제는 Published 타입을 참고하세요.

 

3. 프로퍼티 && 함수

1. associatedtype Output

  • 필수 구현
  • 정의 : Publisher가 발행하는 값의 유형입니다.

 

2. associatedtype Failure : Error

  • 필수 구현
  • Error을 상속해야함
  • 정의 : Publisher가 발행할 수 있는 오류의 유형입니다.

 

3. receive(subscriber:)

func receive<S>(subscriber: S) 
where S : Subscriber, 
      Self.Failure == S.Failure, 
      Self.Output == S.Input
  • 필수 구현
    • Publisher 프로토콜을 준수하는 모든 타입은 이 메서드를 반드시 구현해야합니다.
  • 지정된 Subscriber를 이 Publisher에 연결합니다.
  • Subcriber의 Failure타입과 Input 타입이 Publisher의 Failure 타입과 Output타입이 같아야함.
  • 매개 변수: subscriber
    • 이 Publisher에 연결할 Subscriber
    • Subscriber가 연결된 후, 값을 받을 수 있음
  • 기본적으로 제공되는 subscribe(_:) 구현은 이 메서드를 호출합니다.
    • subscribe(_:)를 사용하면 receive(subscriber:)를 사용하지 않아도 되나?

 

4. subscribe(_:)

func subscribe<S>(_ subscriber: S) 
where S : Subscriber, 
      Self.Failure == S.Failure, 
      Self.Output == S.Input
  • 지정된 Subscriber를 이 Publisher에 연결합니다.
  • 연결된 후 매개 변수의 Subscriber는 값을 받을 수 있다.
  • Publisher의 Output 타입과 Subscriber의 Input 타입이 같아야함
  • Publisher의 Failure 타입과 Subscriber의 Failure 타입이 같아야함
  • 항상 receive(subscriber:) 대신 subscribe(_:)를 호출해야합니다.
    • Publisher를 구현하는 클래스는 반드시 receive(subscriber:)를 구현해야 합니다.
    • Publisher의 기본 subscribe(_:)구현은 내부적으로 receive(subscriber:)를 호출합니다.

 

5. subscribe(_:)

func subscribe<S>(_ subject: S) -> AnyCancellable 
where S : Subject, 
      Self.Failure == S.Failure, 
      Self.Output == S.Output

 

  • 함수 선언
    • S는 Subject 프로토콜을 준수하는 타입
    • Publisher의 Output과 Subject의 Output 타입이 같아야함
    • Publisher의 Failure 타입과 Subject의 Failure 타입이 같아야함
    • 반환 값 : AnyCancellable
      • 구독을 해제할 수 있는 객체
      • 이를 저장해 두었다가 필요할 때 구독을 해제할 수 있다.
    • 매개변수 : subject
      • Publisher에 연결한 Subject
      • Subject가 연결되면 Publisher가 발행하는 값을 받을 수 있음
  • Subject
    • Publisher와 Subscriber의 중간 역할
    • 값을 직접 발행할 수 도 있고, 다른 Publisher의 값을 받아 다시 방출할 수도 있다.

 

6. map(_:)

func map<T>(_ transform: @escaping (Self.Output) -> T) -> Publishers.Map<Self, T>
  • Upstream Publisher에서 수신한 모든 요소를 제공된 클로져를 사용하여 변환합니다.
  • 함수 선언
    • T : 변환된 요소의 타입
    • transform : Self.Output 타입의 요소를 받아 T 타입의 요소로 변환하는 클로저
    • 반환 값 : 변환된 요소를 발행하는 Publisher (Publishers.Map<Self, T> 타입)

 

7. tryMap(_:)

func tryMap<T>(_ transform: @escaping (Self.Output) throws -> T) -> Publishers.TryMap<Self, T>
  • 변환 과정에서 오류를 발생시킬 가능성이 있는 경우 사용
  • 즉, 클로져 안에서 오류를 발생시킬 가능성이 있을 때 사용

 

8. mapError(_:)

func mapError<E>(_ transform: @escaping (Self.Failure) -> E) -> Publishers.MapError<Self, E> where E : Error

 

  • tryMap(_:)은 오류를 던질 수 있는 연산이므로, 오류 타입이 Error로 고정됨
  • mapError(_:)를 사용하면 오류타입을 통일하여 다운스트림 연산자가 더 쉽게 오류를 처리할 수 있음
  • 특정 publisher가 다른 publisher와 결합 될때, 오류 타입이 다르면 연산이 불가능하다.
  • 즉, 오류 타입을 일치시키는 역할을 한다.

 

9. replaceNil(with:)

func replaceNil<T>(with output: T) -> Publishers.Map<Self, T> where Self.Output == T?
import Combine

let values: [Double?] = [1.0, 2.0, nil, 4.0, 5.0]

let cancellable = values.publisher
    .replaceNil(with: 0.0)
    .sink { print("\($0)", terminator: " ") }

// 출력 결과: "1.0 2.0 0.0 4.0 5.0 "
  • 업스트립에서 Publisher가 Optional 값ㅇ르 방출하는 경우, nil 값을 특정 값으로 치환 가능

 

10. scan(_:_:)

func scan<T>(
    _ initialResult: T,
    _ nextPartialResult: @escaping (T, Self.Output) -> T
) -> Publishers.Scan<Self, T>

 

import Combine

// 1️⃣ 배열을 퍼블리셔로 변환
let numbers = [1, 2, 3, 4, 5].publisher

// 2️⃣ scan을 사용하여 누적 합계를 계산
let cancellable = numbers
    .scan(0, +)
    .sink { print("누적 합: \($0)") }
    
/*
입력값 누적 합계
0    0 + 1 = 1
2	 1 + 2 = 3
3	 3 + 3 = 6
4	 6 + 4 = 10
5	 10 + 5 = 15
*/
  • 이전 결과값과 현재 결과 값을 이용하여 누적된 값을 생성하는 연산자
  • 매개 변수
    • initailResult
      • 첫 번째 계산에 사용될 초기값
      • 이전 값이 없을 대 기본값으로 사용됨
    • nextPartialResult
      • 이전 값과 현재 값을 인자로 받아 변환된 값 반환
      • 매번 Publisher가 새로운 값을 방출할 때 실행됨
  • 반환값 
    • 누적된 결과값을 새롭게 방출하는 Publisher (Publishers.Scan<Self, T>)

 

11. filter(_:)

func filter(_ isIncluded: @escaping (Self.Output) -> Bool) -> Publishers.Filter<Self>
  • 주어진 조건을 만족하는 요소만 발행하는 연산자
  • 클로져를 사용하여 각 요소를 검사하고, true를 반환하는 요소만 다운스트림(Subscriber)로 저장

 

12. compactMap(_:)

func compactMap<T>(_ transform: @escaping (Self.Output) -> T?) -> Publishers.CompactMap<Self, T>
  • 옵셔널 값을 포함하는 스트림에서 nil 값을 제거하고, nil이 아닌 값을 방출

 

13. removeDuplicates()

func removeDuplicates() -> Publishers.RemoveDuplicates<Self>

 

  • 이전의 요소와 동일한 값을 가진 요소를 필터링하여 중복된 값을 제거
  • 연속된 중복 값만 제거( 이전에 나타난 값이라도 연속되지 않으면 제거되진 않음
  • Output 타입이 Equatable을 준수해야 사용가능
    • 즉, 비교연산(==)을 통해 중복 요소를 판단할 수 있어야함

14. removeDuplicates(by:)

func removeDuplicates(by predicate: @escaping (Self.Output, Self.Output) -> Bool) 
	-> Publishers.RemoveDuplicates<Self>
let words = ["Swift", "swift", "Combine", "COMBINE", "filter"].publisher

let cancellable = words
    .removeDuplicates { $0.lowercased() == $1.lowercased() }
    .sink { print($0) }

// 출력 결과:
// Swift
// Combine
// filter
  • 사용자 정의 비교 클로져를 제공할 수 있음

 

15. replaceEmpty(with:)

func replaceEmpty(with output: Self.Output) -> Publishers.ReplaceEmpty<Self>
let numbers: [Double] = []
cancellable = numbers.publisher
    .replaceEmpty(with: Double.nan)
    .sink { print("\($0)", terminator: " ") }


// Prints "(nan)".

let otherNumbers: [Double] = [1.0, 2.0, 3.0]
cancellable2 = otherNumbers.publisher
    .replaceEmpty(with: Double.nan)
    .sink { print("\($0)", terminator: " ") }


// Prints: 1.0 2.0 3.0

 

  • 업스트림 Publisher가 아무런 요소도 방출하지 않고 종료될 경우, 미리 지정한 기본값을 대신 방출하는 연산자
  • 요소가 하나라도 있으면 그대로 방출한다.
  • 아무 요소도 방출하지 않고 종료되면 output 값 방출한다.

 

16. replaceError(with:)

func replaceError(with output: Self.Output) -> Publishers.ReplaceError<Self>
  • Publisher에서 오류가 발생했을 때 제공된 기본값으로 대체하고, 정상적으로 스트림을 종료하는 연산자
  • 오류를 감지하면 즉시 대체 값 (output)을 방출한 후, 스트림을 정상적으로 완료
  • 오류를 복구할 필요가 없고, 특정 기본값을 반환한 후 종료하는 경우 유용

 

17. collect()

func collect() -> Publishers.Collect<Self>
import Combine

let numbers = (0...10)
let cancellable = numbers.publisher
    .collect()
    .sink { print("\($0)") }

// 출력: "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
  • 업스트림 퍼블리셔로부터 수신한 모든 요소를 수집하여, 업스트림 퍼블리셔가 완료될 때 단일 배열로 방출하는 연산자입니다.
  • 업스트림 퍼블리셔가 완료되기 전까지 모든 요소를 메모리에 저장하며, 완료 시점에 한 번에 배열로 전달합니다.
  • 만약 업스트림 퍼블리셔가 오류를 방출하면, 수집된 요소 없이 오류를 그대로 다운 스트림으로 전달합니다.
  • 메모리 사용량을 주의해야함
    • 업스트림 퍼블리셔가 완료될 때 까지 모든 요소를 메모리에 저장하므로, 요소의 수가 많을 경우 메모리 사용량이 크게 증가할 수 있다.

 

18. ignoreOutput()

func ignoreOutput() -> Publishers.IgnoreOutput<Self>
  • 업스트림 퍼블리셔의 모든 요소를 무시하고, 완료 또는 실패와 같은 완료 이벤트만을 다운 스트림으로 전달하는 연산자 입니다.
  • 데이터 자체보다는 퍼블리셔의 완료 여부에만 관심이 있을 때 유용하게 사용됩니다.
import Combine

struct NoZeroValuesAllowedError: Error {}
let numbers = [1, 2, 3, 4, 5, 0, 6, 7, 8, 9]
let cancellable = numbers.publisher
    .tryFilter { anInt in
        guard anInt != 0 else { throw NoZeroValuesAllowedError() }
        return anInt < 20
    }
    .ignoreOutput()
    .sink(
        receiveCompletion: { print("completion: \($0)") },
        receiveValue: { print("value: \($0)") }
    )

// 출력: "completion: failure(NoZeroValuesAllowedError())"

 

 

19. reduce(_: _:)

func reduce<T>(
    _ initialResult: T,
    _ nextPartialResult: @escaping (T, Self.Output) -> T
) -> Publishers.Reduce<Self, T>

 

  • 업스트림 퍼블리셔가 방출하는 여러 값을 하나의 축약된 값으로 결합하여, 업스트림 퍼블리셔가 완료될 때 최종 결과를 방출하는 연산자입니다.
  • 매개변수
    • initialResult : 초기 누적 값으로, 연산이 시작될 때 사용된다.
    • nextPartialResult : 업스트림 퍼블리셔가 방출하는 각 값과 현재 누적 값을 결합하여 새로운 누적 값을 생성하는 클로저 입니다.
  • 반환값
    • 업스트림 퍼블리셔의 모든 값을 결합하여 최종 결과를 방출하는 퍼블리셔입니다.
    • 만약 업스트림 퍼블리셔가 에러를 방출하면, 에러를 다운스트림으로 전달하고, 최종 값을 방출하지 않습니다.
  • 주의 사항 
    • 업스트림 퍼블리셔가 완료될 때까지 최종 값을 방출하지 않으므로, 무한 스트림이나 완료되지 않는 퍼블리셔와 함께 사용하면 메모리 누수가 예상치 못한 동작이 발생할 수 있습니다.
    • 업스트림 퍼블리셔가 에러를 방출하면, 최종 값을 방출하지 않고 에러를 전달하며 스트림이 종료됩니다.

 

 

 

 

 

 

출처

번역과 상세 설명은 챗 지피티의 도움을 받았습니다.

 

https://developer.apple.com/documentation/combine

 

Combine | Apple Developer Documentation

Customize handling of asynchronous events by combining event-processing operators.

developer.apple.com