아요 개발 일기

[Combine] - Publisher, Subscriber 본문

iOS/Combine

[Combine] - Publisher, Subscriber

소진이 2023. 1. 16. 09:24

안녕하세요 소진입니다!

 

요즘 SwiftUI를 계속해서 공부하고있는데,

Combine은 무조건무조건 필수로 알아야겠더라구요!

RxSwift하느라 이제야 제대로 살펴보네요..

 

대충 사용은 해봤는데 정확히 알고 있는 것 같지는 않아서 깊게 들여다보려고 

이렇게 포스팅하게 되었습니다!

많이 늦었지만 그만큼 빡세게 해보겠습니다 ㅎ

 


 

Combine ? 🧐

 

apple에서 설명하는 combine은 

"이벤트 처리 연산자를 결합하여 비동기 이벤트 처리를 Customize"

하는 Framework입니다.

 

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

 

Apple Developer Documentation

 

developer.apple.com

 

음..

비동기 이벤트처리하는 프레임워크인가?

Overview를 보면서 더 자세히 알아봅시다!

 

 

Combine 프레임워크는 시간에 따라 값을 처리하기 위한 선언적(declarative) Swift API를 제공합니다.

이러한 값들은 다양한 종류의 비동기 이벤트를 나타낼 수 있습니다.

Combine은 시간이 지남에따라 변경될 수 있는 값을 노출하도록(expose) 선언하고

subscribers는 publishers로부터 해당 값들을 받도록 선언합니다.

 

 

오? 그러니까,

RxSwift와 비슷하게 비동기 처리를 하는 프로토콜이네요!

그렇다면 Combine을 사람들이 그렇게 많이 사용하는 이유가 뭘까요??

 

 

이벤트 처리 코드를 중앙 집중화(centralizing)하고 중첩된 closures 및 콜백과 같은

까다로운 기술을 제거하여 코드를 더 쉽게 읽고 유지 관리할 수 있습니다.

 

오호라..^^..

자, 그럼 본격적으로 주요 컴퍼넌트를 알아볼까요??!!

 


 

Publisher

 

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

 

Publisher는 프로토콜이며 이름 뜻으로 보면 발행자? 역할을 하는 친구 같죠?

 

정의와 OverView를 봅시다!

 


타입이 시간에 따라 일련의 값을 전송 할 수 있음을 선언합니다.

Publisher는 하나 이상의 Subscriber 인스턴스에 element를 제공합니다.

Subscriber Input과 Failure 유형은 Publisher가 선언한 output과 Failure에 일치해야합니다.

Publisher는 Subscriber을 accept하는 OutputFailurereceive(subscriber:) 메소드를 구현합니다.


 

Subscriber

 

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

 

Subscriber은 구독자!겠지요?

 


Publisher로부터 input을 받을 수 있는 형식을 선언하는 프로토콜입니다

Subscriber의 인스턴스는 Publisher으로부터 element stream과

그들의 relationship의 변경사항을 설명하는 life cycle 이벤트를 받습니다.


 

뭐..

정의는 알았으니, 

직접 사용하러 갑시다!!!

 

실습하기

 

Publisher

발행?할 수 있는 방법은 두가지 입니다!

 

let publisher = Just(sojin)

 

Just를 사용하면 단일(하나) 데이터를

 

let arrayPublisher = [1, 3, 5, 7, 9].publisher

 

.publisher를 사용하면 여러개 데이터를 발행?할 수 있습니다!

 

Subscriber

 

1. sink

2. subscribe

3. assign(to: on:)

 

sink를 많이 사용하니까 sink 먼저 알아보겠습니다!

 

1. sink

 

sink는 아래 이미지와 같이 두가지 있습니다!!

 

 

위에꺼는 그냥 데이터만 받고

밑에는 데이터도 받고 완료했을때 처리도 해줍니다!

 

코드로 봅시당

let subscription = publisher.sink { value in
    print("Received Value: \(value)")
}

let subscription2 = arrayPublisher.sink { value in
    print("finished")
} receiveValue: { value in
    print("Received ArrayValue: \(value)")
}

[출력]
Received Value: sojin
Received ArrayValue: 1
Received ArrayValue: 3
Received ArrayValue: 5
Received ArrayValue: 7
Received ArrayValue: 9
finished

간단하죠?!

 

아래처럼

publisher 선언부에서 바로 sink를 호출도 가능합니다!

let subscription = Just("Hello").sink { value in
    print("바로 쓰기! : \(value)")
}

[출력]
바로 쓰기! : Hello

 

 

 

2. subscribe 

 

subscribe의 파라미터 타입은 Subscriber인데,

Subscriber은 프로토콜이라고 위에서 설명했죠???

 

 

 Subscriber 프로토콜을 지정해주면

위 이미지처럼 요구하는 프로퍼티와 메소드가 있습니다.

 

여기서 Input 은 subscriber가 받는 데이터의 타입을 적어주면되고

Failure은 subscriber가 받을 수 있는 Error의 종류를 말합니다.

(subscribe가 Error를 받을 수 없는 경우 Never을 적으면 됩니다)

 

Subscriber의 Overview를 보면 receive 메서드를 호출하여 사용하는 걸 볼 수 있는데,

코드로 직접 사용해보겠습니다! :)

 

class SojinSubscriber: Subscriber {
    
    typealias Input = Int
    typealias Failure = Never
    
    // 1. Subscriber에게 Publisher를 성공적으로 구독했으며 item을 요청할 수 있음을 알림
    func receive(subscription: Subscription) {
        print("구독 시작")
        subscription.request(.unlimited)
    }
    
    // 2. Subscriber가 받을 것으로 예상하는 추가 요소 수를 나타내는 인스턴스
    func receive(_ input: Input) -> Subscribers.Demand {
        print("\(input)")
        return .none
    }
    
    // 3. Subscriber에게 Publisher가 정상적으로 또는 오류와 함께 게시를 완료했음을 알립
    func receive(completion: Subscribers.Completion<Never>) {
        print("완료", completion)
    }
}

arrayPublisher.subscribe(SojinSubscriber())

[출력]
구독 시작
1
3
5
7
9
완료 finished

 

아하...!

데이터 진행 과정을 알 수 있겠네요

 

그럼.. 

.unlimited와 .max 같은건 뭘까요?

 

 

.max(Int) : 최대로 보여줄 itme 수

.unlimited : Publisher가 elements 모두

.none : Publisher의 elements 없음 (출력 안 함)

 

각 필요에 따라서 선택하면 됩니당

 

아래와 같이 Switch case 문을 사용해서 응용도 가능합니다!

 

class SojinSubscriber: Subscriber {
    
    typealias Input = Int
    typealias Failure = Never
    
    //1. 구독자에게 게시자를 성공적으로 구독했으며 항목을 요청할 수 있음을 알림
    func receive(subscription: Subscription) {
        print("구독 시작")
        subscription.request(.max(1))
    }
    
    //2. 구독자가 받을 것으로 예상하는 추가 요소 수를 나타내는 인스턴스
    func receive(_ input: Input) -> Subscribers.Demand {
        print("\(input)")
        
        switch input {
        case 1:
            return .max(1)
        default:
            return .none
        }
        
    }
    
    //3. 구독자에게 게시자가 정상적으로 또는 오류와 함께 게시를 완료했음을 알립니다.
    func receive(completion: Subscribers.Completion<Never>) {
        print("완료", completion)
    }

}

[출력]
구독 시작
1
3

 

 

1,2 번 두개다 max(1)이면,

element가 1개만 나오면 될 것 같은데

왜 2가 나오지? 라고 생각할 수 있습니다!

 

이유는

1번에서 먼저 1개를 출력하여 0번째는 무조건 출력이되겠죠?

그러면 다음 데이터는 1번째니까 case 1에 걸리게되고

max(1)이 실행됩니다!

그래서 1(0번째)과3(1번째)이 출력되는거죠!

 

즉, 1번과 2번 실행이 중첩되서 데이터가 나오는 겁니다!

 

그래서 case문에 0 혹은 2 이상의 수가 들어가면

 

구독 시작
1

 

위와같은 출력이 나와요!

1번째가 해당하지 않기때문에 default에 걸리거든요!!!!

이해.. 가시나요?,,,

 

 

sink에서도 비슷한 작업을 할 수 있는 메서드가 있는데요!

sink(receiveCompletion: ) 입니다ㅎㅎ

코드를 봅시다!

 

let subscription3 = publisher.sink (receiveCompletion: { (result) in
    switch result {
    case .finished:
        print("finished")
    case .failure(let error):
        print(error.localizedDescription)
    }
}, receiveValue: { (value) in
    print(value)
})

[출력]
sojin
finished

 

sink는 클로저 기반으로 동작하네요!!

 

susubscribe나 sink는

각자 상황에 맞는 거 사용하면 될 것 같아요 ㅎㅎ

 

 

3. assign 

휴.,, 드디어 assign,,

 

class MyClass {
    var property: Int = 0 {
        didSet {
            print("Did set property to \(property)")
        }
    }
}

let object = MyClass()
let subscription4 = 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

 

좀 복잡하죠??

위의 arrayPublisher을 MyClass에 있는 property에 할당해주는 방식입니다!

 


 

Subscriber & Publisher Pattern

 

출처&nbsp;https://developer.apple.com/videos/play/wwdc2019/722/

 

위 사진처럼 Publisher와 Subscriber간의 통신이 이루어집니다!!

 

 

너무 길었죠..

고생 많으셨습니다!

다음은 Subject에 대해 알아보도록하겠습니다!!ㅎㅎ

 

 

코드

https://github.com/sojin2/Combine

 

GitHub - sojin2/Combine: Combine을 공부하는 공간입니다.

Combine을 공부하는 공간입니다. Contribute to sojin2/Combine development by creating an account on GitHub.

github.com

 

'iOS > Combine' 카테고리의 다른 글

[Combine] - Subject  (0) 2023.01.16