[Combine] Publisher

2022. 5. 8. 17:01Combine

반응형

이전에는 Combine이 무엇인지 어떤 목적을 위해 만들어졋는지에 대해 간단히 알아보았습니다.

이번에는 그 중에서 Publisher라는 것에 대해서 알아보려고 합니다 🙃

 

Publisher란?

이전 맛보기 글에서는 Publisher는 이벤트를 방출하는 주체라고만 간단히 설명했었는데요. 애플의 설명에서도 시간의 흐름에 따라 값을 방출하는 주체라고 설명되어 있습니다.

 

시간의 흐름에 따라 값을 방출한다는데 이 말이 잘 이해가 안되면 아래 그림을 보면 조금 이해가 편할 것 같습니다. (RxSwift를 공부하신 분이라면 조금 익숙한 그림이죠?? 😎)

Publisher에서 방출한 값은 하나 또는 그 이상의 Subscriber에게 전달된다고 하는데요. 그러면 이제 이 Publisher를 구독하고 있는 곳에서 이 값에 따라 핸들링을 하게 되는 거겠죠 ㅎㅎ..

 

예를 들어, 뷰라고하면 1초에 화면에 “Hi”가 그려지고 2초에 “I’m“이 그려지고 하는 형식이됩니다.

 

Publisher 구현체

Publisher가 시간에 따른 값을 방출하는 주체다. 그렇다면 Swift에서는 Publisher를 어떻게 구현하고 있을까요?

우선 기본적으로 프로토콜로 구현되고 extension을 이용해 수평확장하는 구조를 가지고 있습니다.

protocol Publisher {
    associatedtype Output
    associatedtype Failure : Error

    func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}
  • Output : 기본적으로 Publisher가 값을 방출하는 주체이기 때문에, 어떤 타입의 값을 방출할지를 associatedtype으로 선언한 것입니다
  • Failure : Publisher가 예상치 못한 에러를 방출해야하는 경우 내보내는 타입입니다. 만약, Publisher가 에러를 방출하지 않는 경우 Never로 사용해줍니다
  • receive(subscriber: Subscriber) : 이 부분은 다음 포스팅에서 알아보겠지만, Publisher의 subscriber(subscriber: Subscriber) 메소드가 불렸을 때, 구독자를 받았다고 알리는 메소드입니다. 꼭 구현되어야 하는 메소드입니다.

 

Publisher가 프로토콜로 구현되어 있기 때문에, 직접 사용하기 위해서는 개발자가 채택하고 구현해주어야 하는데 애플에서는 이를 권장하고 있지 않습니다 ㅎㅎ.. 먼저 애플에서 권장하는 방법들을 볼게요.

 

1. PassthroughSubject : Subject의 subclass인 해당 객체를 이용해서 on-demand 방식으로 값을 발행할 수 있는 객체를 이용

let subject = PassthroughSubject<Int, Never>()
subject
    .sink(receiveValue: { value in
        print("Value : \(value)")
    })
    .store(in: &self.cancellables)
        
subject.send(10)

// Value : 10

2. CurrentValueSubject : 새로운 값이 할당될 때마다 방출하는 객체를 이용하는 방법입니다. 역시 Subject의 subclass로 다만 현재 할당된 값에 접근이 가능합니다 ㅎㅎ..

let subject = CurrentValueSubject<Int, Never>(10)
subject
    .sink(receiveValue: { value in
        print("Value : \(value)")
    })
    .store(in: &self.cancellables)

print("Current Value : \(subject.value)")
subject.send(15)
print("Current Value : \(subject.value)")
// Current Value : 10
// Value : 15
// Current Value : 15

3. @Published : propertyWrapper로 구현된 해당 annotation을 이용해 값이 업데이트 될 때마다 방출할 수 있습니다.

class TextManager {
    @Published var text: String = "Initial"

    init() {
        self.$text
            .sink(receiveValue: { text in
                print("Text : \(text)")
            })
            .store(in: &self.cancellables)
        
        text = "Hi"
    }
}

// Text : Initial
// Text : Hi

이렇게 개발자가 직접 Publisher를 구현하는 것이 아닌 애플에서 권장하고 있는 방법을 3가지 알아보았습니다. Publisher를 사용할 일이 있으면 이렇게 3가지를 이용하는 것이 좋을 것 같네요 ㅎㅎ..

 

추가로 Combine Framework에서 제공해주는 Publisher 구현체들이 있습니다. 더 알아보도록할게요~

 

  • Just : 딱 한 번의 Output을 방출해주는 구현체입니다. 한 번의 방출 후에 Completion이 들어오게 됩니다.
let just = Just<Int>(10)
just
    .sink(receiveCompletion: { completion in
        print("Completion : \(completion)")
    }, receiveValue: { value in
        print("Value : \(value)")
    })
    .store(in: &self.cancellables)

// Value : 10
// Completion : finished
  • Future :  역시 단 한번의 Output을 방출해주는 구현체입니다. 다만, 비동기에 대한 값을 방출할 때 유용한 구현체입니다.
let future = Future<Int, Never> { promise in
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        promise(.success(10))
    }
}

future
    .sink(receiveCompletion: { completion in
        print("Completion : \(completion)")
    }, receiveValue: { value in
        print("Value : \(value)")
    })
    .store(in: &self.cancellables)

// Value : 10
// Completion : finished
  • Deffered : 새로운 Subscriber가 등록될 때마다 새로운 Publisher를 생성해 구독되어지는 구현체입니다.
let deffered = Deffered { Just(Void()) } 
deffered
    .sink(receiveValue: { print("Deffered") }
    .store(in: &self.cancellables)

// Deffered
  • Empty : 아무런 값도 방출하지 않으며, 종료되는 구현체이다. (개인적으로 언제 사용할 수 있을지 의문이네요 🤔)
let empty = Empty<Void, Never>(completeImmediately: true)
empty.sink(receiveCompletion: { completion in
        print("Completion : \(completion)")
    }, receiveValue: { value in 
        print("Value : \(value)")
    })
    .store(in: &self.cancellables)

// Completion : finished

let empty2 = Empty<Void, Never>(completeImmediately: false)
empty2.sink(receiveCompletion: { completion in
        print("Completion : \(completion)")
    }, receiveValue: { value in 
        print("Value : \(value)")
    })
    .store(in: &self.cancellables)

// ... Nothing
  • Fail : 즉시 Error와 함께 종료되는 구현체입니다.
let fail = Fail<Void, Error>(error: NSError(domain: "Error", code: -1))
fail.sink(receiveCompletion: { completion in
    print("Completion : \(completion)")
}, receiveValue: { value in
    print("Value : \(value)")
})
.store(in: &self.cancellables)

// Completion : failure(error....)
  • Record : 기록된 Input, Completion을 나중에 Subscriber가 새롭게 등록되었을 때, 반복할 수 있는 구현체입니다.
let record = Record<Int, Never> { recoding in
    print("@Start Recording")
    recoding.receive(1)
    recoding.receive(2)
    recoding.receive(completion: .finished)
}

record.sink {
    print($0)
} receiveValue: {
    print($0)
}.store(in: &self.cancellables)
// @Start Recording
// 1
// 2
// finished

record.sink {
    print($0)
} receiveValue: {
    print($0)
}.store(in: &self.cancellables)
// 1
// 2
// fini

 

오늘은 이렇게 Combine Framework의 한 요소인 Publisher에 대해 알아보았습니다 🙃

다음 포스팅에서는 Subscriber과 나머지 요소들에 대해서도 알아보도록할게요

반응형

'Combine' 카테고리의 다른 글

[Combine] Combine 맛보기  (0) 2022.05.07