본문 바로가기

iOS/Combine Framework

[Swift] No2. Publihser, Subscriber. 개념 파해치기 in Combine

안녕하세요.

Publihser와 Subscriber을 공부하면서 배운 개념들을 소개합니다.

  What is Pubilsher?

 이전 파트에서 소개 했지만 다시 간력하게 설명하겠습니다. "시간의 흐름"에 따라 Publisher's Output type의 값을 하나 또는 여러 Subscriber에게 publish(전송) 전송할 수 있습니다. 주의해야 할 것은 Publisher's Output, Failure generic type 과 Subscriber's Input, Failure generic type이 일치해야만 Publisher는 Subscriber에게 값을 전달할 수 있습니다.

 

 

 지금 자판기가 있습니다. 자판기에는 많은 음료수가 있습니다. 많은 사람이 자판기를 이용하기 위해 줄을 서 있습니다. 음료수를 마시고 싶어 하는 사람이 자판기에 원하는 만큼 버튼을 누를 경우 해당 음료가 나옵니다. 사람마다 음료를 뽑고 싶어하는 개수가 다릅니다. 자판기에 음료가 떨어진다면 더 이상 음료를 뽑을 수 없습니다. 주인이 다시 자판기에 음료를 넣지 않는 이상 불가능 합니다.

 

 Publisher는 자판기와 같습니다. 값(음료)을 가지고 있어야 특정 시간대에 자판기를 이용하려는 사람(Subscriber)에게 사람이 요구하는 만큼의 음료(값)를 줄 수 있습니다.

 

 Swift에서 Publisher는 프로토콜입니다. 따라서 이를 채택하는 Convenience Publishers로 아래와 같은 publisher가 있습니다. Swift에선 어떻게 자판기(Publisher)를 만들 수 있을까요?

 

 (참고로 이들은 Convenience Publisher이기 때문에 Publishers.sequence 등 Publihsers를 붙이지 않아도 됩니다!)

 

 이 중 몇개의 Publisher를 사용해서 publisher를 만들려고 합니다. 특정 버튼에 위치한 음료의 음료수 바코드를 1,2,3,4,5,6 이라고 가정하겠습니다. ( 뽑아 먹을 수 있는 음료수 6개가 있다는 것입니다!! )

let requestBeverage = Notification.Name("RequestBeverage")
let publisher1 = [1,2,3,4,5,6].publisher
let publisher2 = Just([1,2,3,4,5,6])
let publisher3 = Future<[Int],Never> { promise in
    promise(.success([1,2,3,4,5,6]))
}
let publisher4 = NotificationCenter.default.publisher(for: requestBeverage, object: nil)
NotificationCenter.default.post(name: requestBeverage, object: [1,2,3,4,5,6])
class temp {
    @Published var publisher5 = [1,2,3,4,5,6]
}

 

 

 이런 형식의 publisher를 선언할 수 있습니다. 모두 6개의 음료. 각각 1,2,3,4,5,6 바코드가 새겨진 음료가 있습니다. Subject를 통해서도 pubhser를 만들 수 있지만 생략했습니다. 공통점으로 누군가가 와야 자판기에서 음료가 publish될 수 있습니다. 이때 음료를 받을 누군가는 Subscriber입니다.

  What is Subscriber?

sequence의 값을 가지고 있는 publisher에게 subscriber를 구독할 경우 publisher로부터 값을 받을 수 있습니다.

기본적으로 Subscriber를 커스텀해서 만든다고 한다면 Publisher's <Output, Failure> Generic type 과 일치하는 Input,Failure타입을 선언해야 합니다.

 

 Publisher와 Subscriber가 값을 주고 받기 위해서는 subscription이 있어야 합니다. Subscriber는 Publisher에게 subscription을 통해 원하는 값의 개수를 Demand할 수 있고 Publisher는 Subscriber에게 subscription을 통해 값을 전달합니다. 여기서 등장하는 subscription은 Subejct object에서 매우 중요한 요소입니다!!

final class ThirstyPeople: Subscriber {
    typealias Input = Int
    typealias Failure = Never
    //1
    func receive(subscription: Subscription) {
        print("subscription을 통해 연결 OK")
        subscription.request(.max(3))
    }
    //2
    func receive(_ input: Int) -> Subscribers.Demand {
        print("subscription을 통해 음료수 한개 받음. :\(input)")
        return .none
    }
    //3
    func receive(completion: Subscribers.Completion<Never>) {
        print("subscription 해제")
    }
}
var people = ThirstyPeople()
publisher1.subscribe(people)

publisher1는 publish할 수있는 값을 6개 가지고 있습니다. ThirstyPeople통해 Subscriber를 구현했습니다.

 

 초기에 Publisher와 Subscriber가 연결될 때, receive(subscription:)의 인자값 subscription의 subscription.request()를 통해 publisher가 pubilsh 할 수 있는 값을 몇 개 받을지 지정 할 수 있습니다. 이때 subscription과 최초 연결 되었다는 코드를 작성함으로 Publisher와 Subscriber가 연결 되었음을 알 수 있는 코드도 작성할 수 있습니다.

 

 어떤 값을 받은 시점에서 publisher에게 값을 더 많이  달라고 요구할 수도 있습니다. recieve(_:) 구현할 때의 반환값을 .none이 아닌 .max() or .unlimited로 하면 됩니다.

 

 

아직 subscription이 3개나 남았지만 subscriber가 더 이상 값을 받고 싶지 않을 때는 people.reveice(completion: .finished)를 호출하면 됩니다. 또는 publisher가 publish할 수 있는 값 6개를 전부 받으면 됩니다.

 

Subscriber를 부착할 때 매번 프로토콜을 채택해서 구현하는게 아니라 더 쉬운 방법도 존재합니다.

  • sink(_:_:)
  • assign(to:on:)
  • assign(to:)

이들 중 제가 가장 많이 사용하는 sink를 알아보겠습니다!!

 

 "엥? subscriber가 왜 안보이지? " 처음 공부했을 때 엄청 당황했습니다. publisher가 publish하는 값을 받으려면 subscriber와 같은 존재가 있어야 하는데 WWDC의 Combine 소개 영상에선 Publisher, subscriber등을 소개한 후 예시에서 달랑 이 함수를 통해 publisher를 구독했기 때문입니다.

 

sink(_:) 함수는 클로저를 통해서 subscriber를 부착하는 형태입니다. 간단합니다!

 publisher1로부터 .finish()를 받게 된다면 아무리 publisher1에 값을 추가해도 sink 함수를 통해 값을 받을 수 없습니다. (subscription이 연결 해제 됬기 때문입니다.) publisher1가 현재 갖고 있는 모든 값을 publish해도 .finish()를 통한 completion되지 않고 publisher1가 publish할 수있는 새로운 값을 얻게 되면 부착된 subscriber에게 publish할 수 있는 방법은 없을까요? 이와 관련된 게 Subject입니다!!

 

 sink(_:)를 사용하면 subscriber의 demand를 커스텀 하지 않았기에 publisher1로부터 emitted 값을 전부 받게 됩니다. 몇 개만 받을 수 없을까요? 이때 사용하는 것이 operator입니다. publisher는 oerator chains를 통해 값을 다양하게 조절할 수있습니다. subscriber에서 값을 3개만 받도록 sink를 사용하기 위해선 prefix를 사용하면 됩니다. 

publisher1
    .prefix(3)
    .sink { _ in
    print("subsciription 해제")
} receiveValue: { input in
    print("subscription을 통해 음료수 한개 받음 : \(input)")
}

 만일 publisehr 타입이 Subject인 경우 

이와 같이 커스텀 subscriber를 등록한 후에 sink(_:) 함수를 사용할 수도 있습니다.

publisher와 subscriber의 개념에 대해 간략하게 설명했습니다. 

의문점이나 틀린 곳이 있을 경우 알려주시면 감사합니다!!