본문 바로가기
Swift

[Swift] Combine 알아보기 [1] Publisher, Subscriber

by ykr0919 2024. 3. 12.

 

오늘은 Combine이다! 

처음에 Combine을 들었을 때 이거만 생각남 ㅋㅋ

 

이제는 iOS 공부를 하는 사람으로서 Swift Combine을 먼저 떠올려야 한다!! 💡

 

 

Combine의 모든 내용을 다룰 수는 없어서 간단하게 보자!! 

 

지금까지 Combine을 사용해 본 적이 없고 오늘 공부하면서 다 처음 알게 된 내용이다! 

 

엄청 엄청 중요하기 때문에 열심히 알아보자!!!!!! 

 

 

Combine은 이벤트 처리 연산자를 결합하여 비동기 이벤트 처리를 사용자 정의합니다.라고 함. 

 

Combine 효율적인 비동기 처리를 하기 위해 소개됨

 

여기서 비동기와 동기의 차이를 간단하게 보면 

 

 

동기는 요청과 그 결과가 동시에 나오는 것임.  내가 물을 줘!!!라고 말하자마자 웨이터가 물을 바로 가져다주는 거다!! 맞나?? 

 

여하튼 그런 느낌임!! 쉽게 말하면 

 

 

비동기는 요청과 그 결과가 동시가 아니라서 언제 결과가 올지 알 수가 없음

 

모바일 개발에서는 비동기 작업이 엄청 많다고 하는데 그 이유는!! 

 

서버에서 데이터 받아와서 페이지 보여줄 때 네트워크가 안 좋은 곳은 언제 데이터를 받는 게 완료될지 모르는 상황이나   

 

사용자의 버튼 인터랙션(언제 터치할지 모른다) 등등  

 

모바일 앱에서는 사용자의 터지를 받는다던지 서버에서 데이터를 받는다던지 여러 가지 이벤트가 발생

 

그러한 이벤트들이 비동기적으로 발생하다 보니깐 잘 처리하기 위해서 Combine framework 사용 

 

그전에는 여러 방식들을 조합해서 개발하다 보니, 코드가 복잡해짐 

 

일괄된 방식으로 비동기를 처리할 수 있는 API를 Combine으로 제공

 

그렇기에 Combine이 더더욱 중요하다고 할 수 있겠다 ㅎ 

 

 

combine OverView 

 

Combine을 이루는 3가지 주요 컴포넌트가 있는데

 

Combine은 크게 Publisher, Operator, Subscriber로 이루어져 있음

 

간단하게는

Publisher는 생산자 🙎‍♂️

Operator 변경시키는 사람  🙎‍♂️

Subscriber 소비자, 구독자 🙎‍♂️

라고 생각하면 쉬움! 

 

Event Stream(Event pipeline)

 

Subscriber로부터 데이터를 요청받으면 Publisher에서 데이터를 제공하고 중간에 Operator를 거쳐 Subscriber에게 전달

 

 

PublisherSubscriber가 서로 데이터를 주고받을 때는 항상 두 가지의 타입이 존재하는데,

 

Publisher 입장에서는 Output 타입과 Failure 타입이 존재

에러가 발생했을 경우 Failure 타입 그렇지 않다면 Output 타입을 전달

이 데이터를 받는 Subscriber는 Publisher의 output타입과 동일한 Input타입과, 그리고 동일한 Failure타입을 가져야 함 

 

Publisher

 

protocol Publisher {
    associatedtype Output
    associatedtype Failure: Error
    
    func subscribe<S: Subscriber>(_ subscriber: S)
    where S.Input == Output, S.Failure == Failure
}

 

데이터 배출하는 친구

  • 구체적인 Output Failure 타입을 정의함 
  • Subscriber가 요청한 것만큼 데이터 제공

 

빌트인(Built-in) Publisher인 Just, Future가 있다.

  • Just는 값을 다루고 
  • Future는 Function을 다룸   

 

Just는 오직 하나의 값만을 출력하고 끝나게 되는 가장 단순한 형태의 Publisher로 Combine Framework에서 빌트인(Built-in) 형태로 제공하는 Publisher

Just는 인자로 받는 값의 타입을 Output 타입으로 실패타입은 항상 Never로 리턴

 

iOS에서는 자동으로 제공해 주는 녀석들이 있음

NotificationCenter

Timer

URLSession.dataTask

 

Subscriber 

 

protocol Subscriber {
    associatedtype Input
    associatedtype Failure: Error
    
    func receive(subscription: Subscription)
    func receive(_ input: Input) -> Subscribers.Demand
    func receive(completion: Subscribers.Completion<Failure>)
}

 

Publisher에게 데이터 요청함 

Input, Failure 타입이 정의 필요함 

Publisher 구독후, 개수를 요청함 

파이프라인을 취소할 수 있음

빌트인(Built-in) Subscriberassignsink가 있음 

 

assignPublisher가 제공한 데이터를 특정 객체의 키패스에 할당 

sinkPublisher가 제공한 데이터를 받을 수 있는 클로져를 제공함 

 

간단한 예시를 보자! 

 

import Foundation
import Combine

// Publisher & Subscriber

let just = Just(1000)
let subscription1 = just.sink { value in
    print("Received Value: \(value)")
}

let arrayPublisher = [1, 3, 5, 7, 9].publisher
let subscription2 = arrayPublisher.sink { value in
    print("Received Value: \(value)")
}

//Received Value: 1000
//Received Value: 1
//Received Value: 3
//Received Value: 5
//Received Value: 7
//Received Value: 9

 

Just는 위에서 봤듯이 데이터를 한번 전송하고 끝임! 그리고 그 값을 Subscriber후에 print 하고 있음 

 

아래는 배열을 Publisher로 만들어준 후 값을 하나하나씩 보내주고 Subscriber는 그 값을 받아서 print 해줌 

sink는 Publisher가 제공한 데이터를 받을 수 있는 클로져를 제공함 🔼 

 

이어서 assign을 사용해 보면 

 

class MyClass {
    var property: Int = 0 {
        didSet {
            print("Did set property to \(property)")
        }
    }
}
 
let object = MyClass()
let subscription3 = arrayPublisher.assign(to: \.property, on: object)
print("Final Value: \(object.property)")

//Did set property to 1
//Did set property to 3
//Did set property to 5
//Did set property to 7
//Did set property to 9
//Final Value: 9

 

assign는 Publisher가 제공한 데이터를 특정 객체의 키패스에 할당 🔼

 

arrayPublisher의 값을 assign이 object라는 객체 property에 값을 세팅해주고 있음 

 

object는 MyClass()를 가리키고 property 값이 변경될 때마다 print를 실행하고 있음 

 

 

Publisher와 Subscriber 가 어떻게 관계를 맺냐면? 🤔

 

 Publisher & Subscriber  Pattern

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

 

 

Publisher는 계속 콘텐츠를 만든다.

Subscriber가 콘텐츠가 필요해서 Publisher에게 다가가서 구독한다. 

PublisherSubscription을 준다.

Subscriber가 몇 개의 데이터가 필요하다고 요청을 한다.

Publisher가 만든 콘텐츠를 Subscriber가 계속 받는다.

Pulblisher가 종료하게 되면 종료 메시지를 Subscriber가 받는다. 

 

이런 방식으로 동작을 하게 된다!!

 

 

여기서 Subscription은 뭘까??

 

SubscriberPublisher가 연결됨을 나타내는 녀석 

쉽게 생각하면 Publisher가 발행한 구독 티켓 

이 구독 티켓만 있으면 테이터를 받을 수 있음. 이 티켓이 사라지면 구독 관계도 사라짐

 

Cancellable protocol을 따르고 있다. 

따라서 Subscription을 통해 연결을 Cancel 할 수 있다 

 

subject (Publisher)

 

send(_:) 메서드를 이용해서 이벤트 값을 주입시킬 수 있는 퍼블리셔

send라는 곳에 값을 넣으면 Output으로 배출하게 됨

 

기존의 비동기처리 방식에서 Combine으로 전환 시 유용함 

 

2가지 빌트인(Built-in) 타입이 있음 

 

PassthroughSubject 

Subscriber가 달라고 요청하면,

그때부터, 받은 값을 전달해주기만 함

전달한 값을 들고 있지 않음 

 

CurrentValueSubject 

Subscriber가 달라고 요청하면,

최근에 가지고 있던 값을 전달하고, 그때부터, 받은 값을 전달함

전달한 값을 들고 있음

 

예시로 한번 보자!! 

 

import Foundation
import Combine

// PassthroughSubject

let relay = PassthroughSubject<String, Never>()
let subscription1 = relay.sink { value in
        print("subscription1 received value: \(value)")
}

relay.send("Hello")
relay.send("World!")

//subscription1 received value: Hello
//subscription1 received value: World!


// CurrentValueSubject

let variable = CurrentValueSubject<String, Never>("")
let subscription2 = variable.sink { value in
    print("subscription2 received value: \(value)")
}

variable.send("More text")

//subscription2 received value: 
//subscription2 received value: More text

 

CurrentValueSubjectOutput의 초기값을 넣어줘야 함! 

 

let variable = CurrentValueSubject<String, Never>(초기값 자리)

 

CurrentValueSubject의 print 된 결과물을 보면 처음에는 아무런 값이 나오지 않고 두 번째에 More text가 나옴.

처음에 아무런 값이 나오지 않은 이유는 초기값이 "" 이기 때문인데! 

 

여기서 CurrentValueSubject 초기값을 한번 출력해 주고 그다음에 새롭게 주입된 값을 출력해 줌

 

위에서 봤듯이 CurrentValueSubject는 전달한 값을 가지고 있기 때문! 그 후에 그 값을 또 전달하고 새로운 값을 출력해 줌

 

초기값을 안 넣어주면 

 

 

어렇게 오류남! 

 

 

내용이 너무 길어져서 다음 글로 넘기겠다!! 

 

아직 다 덜 끝났음!! 아직

@Published (Publisher), Operator, Scheduler  등등 

공부할게 남았음!! 

 

 

 

간단하게만 봤는데도 궁금한 것도 많이 생기고 처음 본 게 너무 많아서 더 공부해 보자! 

 

 

 

 

 

 

참고 및 인용

 

이준원 강사님

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

https://medium.com/harrythegreat/swift-combine-%EC%9E%85%EB%AC%B8%ED%95%98%EA%B8%B0-%EA%B0%80%EC%9D%B4%EB%93%9C-1-525ccb94af57