본문 바로가기

iOS공통

[Swift] Combine 3편: subscriber동작방식 이해하기

안녕하세요✋ 주디오스 입니다. 

 

 

저번 편에서 publisher(이하 pub)에 대해 다루었습니다.

 

 

이번에는 subscriber(이하 sub)에 대해 글을 작성해 보도록 하겠습니다.

 

 

Subscriber 프로토콜

 

Subsriber프로토콜을 사용하여 앞선 2편에서 다루었던 sink, assign등을 사용하지 않고도 sub를 만들 수 있습니다.

 

 

해당 프로토콜은 아래코드 구현된 매서드와 연관타입을 요구합니다.

 

 

class MySub: Subscriber {
    typealias Input = Date
    typealias Failure = Never
    
    //#1
    func receive(subscription: Subscription) {
        ...
    }
    
    //#2
    func receive(_ input: Date) -> Subscribers.Demand {
        ...
    }
    
    //#3
    func receive(completion: Subscribers.Completion<Never>) {
        ...
    }
}

 

 

먼저 #1 recieve의 경우 pub과의 관계형성시에 최초로 호출되는 매서드입니다. 

 

 

매개변수로 Subsription인스턴스를 전달받는 것을 확인할 수 있습니다. 이 인스턴스를 사용하여 pub과의 관계를 종료할 수 있습니다. 

 

 

따라서 해당 인스턴스를 프러퍼티로 저장해 두는 것이 좋습니다.

 

 

✋✋✋✋✋✋
var subscription: Subscription?

func receive(subscription: Subscription) {
	✋✋✋✋✋✋
	self.subscription = subscription
}

 

 

최종적으로 sub클래스가 Cancellable프토콜을 채택하도록하여 sink, assign으로 생성한 sub처럼 관계를 끊도록 할 수 있습니다.

 

 

class MySub: Subscriber, Cancellable {
	...
    
    var subscription: Subscription?
        
    func cancel() {
        subscription?.cancel()
    }

    func receive(subscription: Subscription) {
        self.subscription = subscription
    }
    
	...

}

 

 

#2 매서드의 경우 pub에 의해 element를 전달받는 매서드 입니다.

 

 

이 메서드에서 주목할 점은 바로 반환 타입입니다.

 

 

 

Subscriber.Demand

 

 

해당 개념은 sub에서 가장 중요한 개념입니다.

 

 

1편에서 간략하게 살펴보았던 내용을 상기시켜 보겠습니다.

 

 

- sub가 pub에게 element를 요구합니다(Demand).

- pub는 sub의 요구에 publish로 반드시 호흥합니다.

 

 

즉 sub가 pub에 요구를 하지 않으면 pub는 element를 publish하지 않습니다.

 

 

sink, assingn의 경우 기본적으로 이 요구의 횟수가 무한대로 설정되어 있습니다.

 

 

따라서 연결을 끊기 직전까지 지속적으로 element를 전달받는 구조입니다.

 

 

요수횟수는 2가지 방법을 설정할 수 있습니다.

 

 

- Subsription인스턴스의 매서드를 사용하여 설정할 수 있습니다.

- #2 receive매서드를 사용하여 요구횟수를 반환하기.

 

 

먼저 첫번째 방법은 아래와 같이 최초로 요구횟수를 전달하는 경우 사용할 수 있습니다.

 

 

//#1
func receive(subscription: Subscription) {
	self.subscription = subscription
	✋✋✋✋✋✋
	subscription.request(Subscribers.Demand.max(3))
}

 

현재까지 pub에 요구된 요청의 횟수는 3회이고 앞으로 3회의 element를 전달받으면 더이상 element를 발행받지 못합니다.

 

 

2번째 방법의 코드는 아래와 같습니다.

 

 

func receive(_ input: Date) -> Subscribers.Demand {    
    return Subscribers.Demand.max(1)
}

 

 

해당 매서드는 element를 전달받을 때 호출된다고 하였습니다. 

 

 

 

만약 최초 요구횟수가 3회인 상태에서 위 매서드가 3번 호출된다면 최초 요구횟수 3회가 충족됩니다.

 

 

하지만 반환되는 요구횟수( Demand.max(1) )에 의해 3번의 요구횟수 누적이 발생하고 미충족 요구횟수가 3회로 설정됩니다.

 

 

이렇게 요구횟수 방식을 설정하여 sub가 pub에게 전달받을 이벤트의 횟수를 조정하는 방식을 Back pressure방식이라고 합니다.

 

 

아래코드는 Back pressure방식을 사용하여 

 

 

정확히 3번의 이벤트만 수령한 코드입니다.

 

import Foundation
import Combine

class MySub: Subscriber {
    typealias Input = Date
    typealias Failure = Never
    
    var subscription: Subscription?

    
    func receive(subscription: Subscription) {
        self.subscription = subscription
        
        ✋✋✋✋✋✋
        DispatchQueue.main.asyncAfter(deadline: .now()+5.0) {
            self.subscription?.request(Subscribers.Demand.max(3))
        }
    }
    
    func receive(_ input: Date) -> Subscribers.Demand {
        print(input)
        
        return Subscribers.Demand.none
    }
    
    func receive(completion: Subscribers.Completion<Never>) {
        print ("--done--")
    }
}

let sub = MySub()

let pub = Timer.publish(every: 1, on: RunLoop.main, in: .default).autoconnect()

pub.subscribe(sub)
print("pub-sub connected \(Date.now)")

///pub-sub connected 2023-08-22 14:03:06 +0000
///2023-08-22 14:03:12 +0000
///2023-08-22 14:03:13 +0000
///2023-08-22 14:03:14 +0000

 

 

#2매서드를 아래처럼 고치면 무한대로 수령이 가능한 것이지요. 감이좀 잡히시나요?🤗

 

 

func receive(_ input: Date) -> Subscribers.Demand {
    print(input)
    
    return Subscribers.Demand.max(1)
}

///2023-08-22 14:05:53 +0000
///2023-08-22 14:05:54 +0000
///2023-08-22 14:05:55 +0000
///2023-08-22 14:05:56 +0000
///2023-08-22 14:05:57 +0000
///2023-08-22 14:05:58 +0000
///2023-08-22 14:05:59 +0000
///2023-08-22 14:06:00 +0000
///2023-08-22 14:06:01 +0000
///2023-08-22 14:06:02 +0000
///2023-08-22 14:06:03 +0000
///2023-08-22 14:06:04 +0000
///...

 

 

Back pressure방식은 pub의 publish를 스케쥴링 하는 것이 목표입니다.

 

 

이 방식은 sub가 Demand를 조정하는 것 외에도 pub에서 다양한 매서드를 호출하 커스터마이징이 가능합니다.

 

 

아래 링크에서 pub의 다양한 매서드를 확인할 수 있습니다.

 

 

예를들어 buffer(size:prefetch:whenFull:)의 경우 상위 pub체인에서 너무 많은 element가 넘어 오는 경우를 대비한 매서드이다.

 

 

 

 

마지막으로 #3의 경우 연결이 종료된 경우에 호출되는 매서드입니다.

 

 

하지만 이는 pub에의한 연결종료시에만 호출됩니다.

 

 

자, 여기까지 Subscriber프로토콜에 대해 알아보았습니다.

 

 

다음 편에서는 지금까지 배운 개념들을 사용하여 기존의 코드를 Combine으로 변경하는 글을 쓰도록 하겠습니다.

 

 

아듀~✋✋✋✋

 

 

 

 

 

Processing Published Elements with Subscribers | Apple Developer Documentation

Apply back pressure to precisely control when publishers produce elements.

developer.apple.com