[Swift] Combine 입문하기 2 — Publisher, Subscribe, Operator

이전편

Harry The Great
해리의 유목코딩
8 min readFeb 9, 2020

--

Publisher의 Subscriber의 종류

다시한번 Publisher와 Subscriber로 돌아와보겠습니다. 그럼 누가 데이터를 제공하는 Publisher가 될 수 있을까요?

Convenience Publisher

  • Just: 위에서 본것과같이 가장 단순한 형태의 Publsiher로 에러타입으로 Never를 갖습니다.
  • Promise: Just와 비슷하지만 Filter Type을 정의할 수 있습니다.
  • Fail: 정의된 실패타입을 내보냅니다.
  • Empty: 어떤 데이터도 발행하지 않는 퍼블리셔로 주로 에러처리나 옵셔널값을 처리할때 사용됩니다.
  • Sequence: 데이터를 순차적으로 발행하는 Publisher로 (1…10).Publisher로 이에 해당합니다.
  • ObservableObjectPublisher: SwiftUI에서 사용되는 ObservableObject를 준수하는 퍼블리셔입니다.

Published 어노테이션

프로퍼티에 Published 어노테이션을 붙으면 ProperyWrapper를 통해 Publisher로 사용할 수 있습니다.

@Published var userName: String = “”

Framework 내장제공

Foundation Framework나 Notification Center처럼 개발자가 프레임워크 애 서 직접적으로 제공하는 Prodiver는 개발자가 Subscribe만 구현하여 손쉽게 사용할 수 있으며 SwiftUI의 ObservableObject는 SwiftUI내에서 발생하는 이벤트들을 Combine을 이용해 처리할 수 있도록 도와줍니다.

Publiser는 이외에도 이외에도 아래쪽에서 설명할 Subject의 서브클래스 열거형 데이터 등등이 있습니다.

Subscriber

Subscriber는 크게 3가지로 구현할 수 있습니다. 첫째로 Subscriber를 상속받아 직접 구현하기 sink를 이용하여 결괏값 받기 asggin을 이용하여 스트림 값을 전달하기입니다. 이번에는 직접 Subscribe을 를 구현해보겠습니다.

sink는 클로저 형태 구독하기때문에 별도의 타입 선언 없이 클로저로 받아 receive와 completion때의 라이프사이클만 접근할 수 있었지만 Subscriber를 직접 구현하면 모든 라이프사이클을 확인할 수 있습니다.

데이터의 구독을 시작합니다.데이터를 받았습니다. A
....중략
데이터를 받았습니다. E
데이터를 받았습니다. F
데이터를 받았습니다. G
모든 데이터의 발행이 완료되었습니다.

unlimited로 된 설정을 바꾸어 구독받을 데이터의 수를 변경할 수 있습니다.

func receive(subscription: Subscription) {
print(“데이터의 구독을 시작합니다.”)
subscription.request(.max(2))
}

만약 구독의 최대수를 2개로 제한한다면 로그는 아래와같습니다.

데이터의 구독을 시작합니다.
데이터를 받았습니다. A
데이터를 받았습니다. B

모든 데이터가 발행되지 않았기 때문에 completion는 호출되지 않습니다.

Subscrions.Demand를 리턴하는 receive 함수는 구독 이후에 데이터 스트림을 변경할 때 사용합니다. none으로 리턴하면 현재 데이터 스트림을 유지한다는 뜻입니다.

func receive(subscription: Subscription) {
print(“데이터의 구독을 시작합니다.”)
//데이터를 2개만 구독합니다.
subscription.request(.max(2))
}
func receive(_ input: String) -> Subscribers.Demand {
print(“데이터를 받았습니다.”, input)
//구독 도중 데이터를 unlimited로 바꾸어주면 이전의 설정을
//무시하고 모두 구독하게됩니다.
return .unlimited
}

위와 같이 변경하면 10개의 데이터를 모두 구독하게 됩니다. 이전에 sink와같이 빌트인 타입을 사용하게되면 이런 모든 과정을 생략할 수 있도록 도와줍니다.

Operator

만약 Publsiher가 String타입이 아닌 Int타입이라면 어떻게 될까요?

당연히 서로 받는 타입이 다르기 때문에 타입 에러가 발생하게 됩니다. 이번에는 Int타입의 Publsiher를 위에서 만든 Subscriber에 연결해보겠습니다.

Opeator는 생성자와 소비자 중간에 위치하여 데이터 스트림을 가공해주는 역할을 합니다. 중간에 위치하여 데이터를 가공하여 보내줄 수 있습니다.

이제 문제없이 데이터가 출력됩니다. 이런 Operator의 종류는 매우 많습니다. 몇가지 대표적으로는

Mapping Element

주로 데이터를 다른 데이터 타입으로 변형하는 역할을 합니다. ex) scan, setFailureType, map, flatMap

Filtering Element

조건에 맞는 데이터만 허용합니다. ex) compactMap, replaceEmpty, filter, replaceError, removeDuplicates

Reduce Element

데이터 스트림을 모아 출력합니다. ex) collect, reduce, tryReduce, ignoreOutput

Mathematic operations on elements

숫자시퀀스값과 관련된 스트림을 제어합니다. ex) max, count, min

Sequence Elements

데이터 시퀀스를 변형할때 사용합니다. ex) prepend, firstWhere, tryFirstWhere, first, lastWhere, tryLastWhere, last, dropWhile

Subject

Subject는 Publsiher의 일종입니다. 특별한 점은 파이프라인 외부에서도 파이프라인 안으로 데이터를 보낼 수 있습니다. 외부에서 안으로 데이터를 주입시킬 수 있기 때문에 기존 Combine 프로토콜이 아닌 콜백 클로저로 구현되어있더라도 Subject를 이용하면 쉽게 리팩터링 할 수 있습니다. 또한 SwiftUI처럼 다른 Framework와도 쉽게 연동하여 사용할 수 있습니다.

Subject는 크게 PassthroughSubject과 CurrentValueSubject 두 가지의 빌트인 타입을 갖습니다.

PassthroughSubject

상태값을 가지지않는 Subject입니다. Suject의 특성상 외부에서 안으로 데이터를 주입시킬 수 있기 때문에 기존 Combine 프로토콜이 아닌 콜백 클로저로 구현되어있더라도 쉽게 리팩토링해줄 수 있습니다.

CurrentValueSubject

상태값을 가지는 Subject로 주로 UI의 상태값에따라 데이터를 발행할때 사용하기 유용합니다. 출력값을 아래와 같습니다.

true //sink와 동시에 초기값이 발행됩니다.
초기값은 true입니다.
false
true

Cancellable

우리가 이전에 학습한 Subscriber는 Cancellable을 리턴 값으로 가집니다. Cancellable은 데이터 발행 중 cancel() 메서드가 호출되었을 때 모든 파이프라인이 멈추고 끝나게 됩니다. 만약 사용자가 데이터 로딩을 기다리던도중 뒤로 간다거나 취소를 누른는경우처럼 스트림을 중단해야할때 사용될 수 있습니다.

위에서 언급한 Subject에서 데이터를 발행하고 cancel 함수를 이용해서 데이터의 발행을 종료해보겠습니다.

PassThoughSubject는 외부에서도 데이터를 발행할 수 있기 때문에 언제든 send 메서드를 이용해서 스트림으로 데이터를 전달할 수 있습니다. cancel이 호출된 이후에는 더 이상 데이터가 발행되지 않습니다.

다음편에는 Combine으로 네트워크요청을 해보겠습니다.

다음편

참고 및 인용

--

--

Harry The Great
해리의 유목코딩

Android & IOS Developer 😀 미디움 이외에 스니펫이나 디버그노트로 활용하는 https://www.harrymikoshi.com/ 블로그도 운영하고있습니다.