4. Combine: Publisher와 Subscriber (2)

김가영·2022년 8월 11일
0

concurrency

목록 보기
4/4
post-thumbnail

Custom Publisher

  • Publisher 프로토콜을 직접 구현하는 대신 Combine 프레임워크가 제공하는 타입들을 이용하면 간편하게 Custom Publisher를 구현할 수 있다.

1. Subject

Subject 는 값을 외부에서 publish할 수 있는 메서드를 제공하는 Publisher protocol이다.

값을 주입할 때에는 send(_:) 메서드를 이용한다.

PassthroughSubject(final class)

  • conforms to Publisher and Subject
  • downstream subscriber들에게 값을 전달한다.
  • Subject protocol의 구현체이다.
  • 초기값이나 버퍼를 가지고 있지 않기 때문에 해당 publisher를 구독하고 있는 subscriber가 없거나, 있더라도 demand가 0이라면 그냥 값을 없애버린다.

CurrentValueSubject(final class)

  • conforms to Publisher and Subject
  • 초기값과 함께 초기화된다.
  • value 프로퍼티를 가지고 있으면서 send(_:) 메서드가 호출될 때마다 value 를 해당 값으로 갱신한다. → value 를 통해 가장 최근에 pubilsh된 값에 접근할 수 있다.
  • subscribe 되면 가지고 있는 최신값, value 를 보내준다.

2. Published

타입 프로퍼티에 @Published 를 추가하여 해당 프로퍼티의 값이 변경될 때마다 값을 보내주는 publisher를 생성할 수 있다. $ 를 프로퍼티 이름 앞에 추가하여 publisher에 접근한다.

  • willSet 블록에서 publishing이 일어난다. 즉, 실제 값이 프로퍼티에 적용되기 전에 subscriber는 값을 받게 된다.
  • class 내 프로퍼티에만 적용 가능하다.

AnyPublisher

고유한 프로퍼티를 가지지 않는, publisher의 구현체이다. upstream publisher로부터 받은 값들과 completion(성공/실패 여부)들을 전달해주는 역할은 유지하고 있으므로 타입 특이적인 정보를 지우는 데(type erasure) 이용된다.

  • 모듈간 publisher를 전달할 때
  • 값을 직접 주입(by send(_:)) 가능한 Subject 등에서 타입에 의존되는 메서드나 프로퍼티를 감추고 싶을 때

이용하면 좋다.

  • publisher의 타입을 노출하지 않으므로, 내부에서 Output, Failure 타입을 변경하지 않는 한에서 publisher 타입이나 내부 구현을 수정해도 이를 이용하는 클라이언트들에게 영향을 끼치지 않는다는 장점이 있다.
  • eraseToAnyPublisher() operator를 통해 publisher를 AnyPublisher로 간단하게 wrap할 수 있다.

Custom Subscriber

Publisher로부터 Subscriber가 값을 받기 위해서는 1. Subscription이 필요하고, 2. Demand(값을 요청하는 것)가 필요하다.

Demand

  • 값을 요청하기 위해서는 Subscribers.Demand 타입을 이용한다.
  • publisher로부터 요청한 만큼의 값만 받기 때문에, 처리 속도에 맞게 조절이 가능하다.
  • 값을 요청하는 방법으로는 두 가지가 있다.
    1. publisher는 subscriber가 처음 구독(subscribe)할 때 Subscription 인스턴스를 제공한다. 해당 인스턴스에 request(_:) 메서드를 호출하여 값을 요청한다.
    2. publisher는 subscriber에게 값을 전달 할 때 subscriber의 receive(_:) 메서드를 호출한다. 메서드의 리턴 값으로 Demand를 리턴한다.
  • 추가로 Demand는 항상 증가하기만 한다. 새로운 Demand는 이전의 요청과 합산되고, Demand로 음수를 보낼 수도 없다.

high or unlimited demand

  • sink(receiveValue:) , assign(to:on:) 을 통해 생성되는 편의 Subscriber는 처음 publisher와 연결되자마자 unlimited Demand를 보낸다.
  • 이런식으로 unlimited 또는 높은 수의 demand를 보내면, subscriber가 요청하는 것보다 빠른 속도로 값이 생성되어 메모리에 부하가 쌓이거나 값이 유실될 수 있다. 그렇기에 주의가 필요하다.
  • 대부분의 UI 관련 publisher들은 그렇게 빠른 속도로 값을 생성하지 않거나 하나의 값만 보내고 종료하기 때문에 sink / assign subscriber를 써도 괜찮다.
  • 직접 demand를 제어하기 위해서는 custom subscriber를 이용하거나, Back-Pressure Operators를 이용해야한다.

custom subscriber

  • subscription을 통해 1개의 값만 요청하고 있으므로 처음 생성된 “Hello” 만 출력됐다.
  • publisher가 completion을 호출하고 있지 않으므로 "--done--" 도 아직 출력되지 않았다.
  • publisher.send(completion: .finished) 를 호출하여 완료하거나, 그 전에 subscription 객체를 이용해서 새로운 demand를 보낼 수도 있다.

Cancellable

A protocol indicating that an activity or action supports cancellation.

  • cancel() 을 호출하여 할당된 리소스들을 제거하고 subscription을 중단한다.
  • AnyCancellable: AnyPublisher처럼 type을 지운 (type-erasing) cancellable object.
    • deinit될 때 자동으로 cancel() 을 호출한다. AnyCancellable로 subscription을 property에 저장해두면 해당 인스턴스가 메모리에서 제거될 때 subscription과 할당된 메모리를 자동으로 해제한다.
    • store(in:) 을 통해 collection이나 set에 cancellable instance를 저장할 수도 있다.

Reference

profile
개발블로그

0개의 댓글