Combine(2)

Combine : How?


Subject

A publisher that exposes a method for outside callers to publish elements.
외부 호출자가 요소를 발행할 수 있는 방법을 제공하는 publisher 프로토콜

Subject는 publisher이지만 발행하는 방법이 다릅니다. Combine에서는 두 가지 subject를 제공합니다.

  • PassThroughSubject
    publisher를 생성할 때 필요했던 필수 코드들을 편리한 방법으로 만들 수 있도록 하는 subject입니다. send(subscription:)이나 send(completion:)으로 subscriber에게 event를 보낼 수 있습니다.
let passThroughSubject = PassthroughSubjectz<String,Never>()
let subscriber = passThroughSubject.sink(receiveValue: { (value) in
	print("\(value)")
})

passThroughSubject.send("Hello")
passThroughSubject.send(completion: .finished)


// OUTPUT
Hello

  • CurrentValueSubject
    value가 바뀔때 마다 새로운 element를 발행하고, single value로 취급하는 subject입니다. 즉 값을 항상 하나만 가지고 있어야 하기 때문에, 초기 생성 시 init값을 주어야 오류가 나지 않습니다.
let currentValueSubject = CurrentValueSubject<String, Never>("Minseo")
let subscriber = currentValueSubject.sink(receiveValue: {
            print("CurrentValueSubject : \($0)")
})
print("\(currentValueSubject.value)")
currentValueSubject.value = "Hello"
print("\(currentValueSubject.value)")
currentValueSubject.send("World")

// OUTPUT
CurrentValueSubject : Minseo
Minseo
CurrentValueSubject : Hello
Hello
CurrentValueSubject : World

출력 값으로 값을 오직 하나만 출력하므로, CurrentValueSubject는 말 그대로 현재 값만을 채택하는 것이라고 생각했습니다.

덧붙여 말하자면, subject는 publisher를 편리하게 쓰기 위한 것이며, 용도에 따라서 passthrough나 currentvalue를 사용하면 되겠다고 여겨졌습니다.



Cancellable

activity나 action의 취소를 지원하는 프로토콜

cancellable은 memory performance를 위해 꼭 써야하는 중요한 프로토콜입니다.

  • cancel()
    cancel()을 호출한다면 할당된 자원을 모두 해제합니다. 이외에도 타이머, 네트워크 액세스, 디스크 I/O 또한 중지시킵시다.

  • Cancellable 과 AnyCancellable

    - Cancellable을 직접 생성하는 경우
    viewcontroller가 deinit되어도 stream이 살아있습니다. 따로 스트림을 담은 후 cancel()을 호출해야 모든 리소스가 해제 됩니다.

    - AnyCancellable로 생성하는 경우
    viewcontroller가 deinit되면 cancel() 호출을 하지 않아도 모든 리소스가 해제됩니다. (Auto-cancel)

var bag = Set<AnyCancellable>()
let subject = CurrentValueSubject<String,Never>("Minseo")

subject.sink(receiveValue: { value in
	print("\(value)")
})
	.store(in: &bag)
   
   
// OUTPUT
Minseo



Scheduler

closure가 언제 실행되고 어떻게 실행되는지를 정의하는 프로토콜

  • closure?
    비동기 이벤트가 발생할 때마다 실행하기 위함

  • DispatchQueue로 Thread 관리
    receive(on: ) 이나 subscribe(on: )을 사용합니다. 이 두개의 차이는 stream의 방향입니다.


그럼 스트림은 무엇일까요?

데이터의 흐름을 표현하기 위해 stream이라는 용어를 사용한다고 합니다.
물이 상류에서 하류로 흐르듯이, 현재 처리하고 있는 시점보다 이전이면 upstream, 이후면 downstream이라고 합니다.

Combine(1)에서 언급한 Zip, combineLatest, Merge 등의 Operator가 upstream publisher를 subscribe하고, downstream subsriber에게 결과를 내보냅니다. 즉, 일반적으로 publisher -> subscriber로 element를 주기 때문에, subscriber로 간다면 downstream, publisher 로 간다면 upstream이겠죠?

Scheduler는 element가 생성된 스레드와 동일한 스레드를 사용합니다.

위 개념으로 미루어 보면 receive는 subscriber로 내려줄 때 스레드를 변경하도록 하고, subscribe는 publisher로 올라가 스레드를 변경하도록 하는 것이라고 생각했습니다.

단, subscribe(on: )의 경우 upstream에 영향을 주어도 "Scheduler는 element가 생성된 스레드와 동일한 스레드를 사용합니다."의 이유때문에 downstream에 대한 스케줄러도 같이 변경되는 경우가 발생합니다.

만약 이를 컨트롤하고 싶다면 receive(on: )을 구현하여 downstream 스케줄러를 명시적으로 설정해야 합니다. 이 얘기는 downstream을 변경하는 용도로 subscribe(on: )를 사용해서는 안된다는 것을 의미합니다.

subscribe(on: )은 현재의 subscription을 생성, 취소 및 입력 요청에 사용할 스케줄러를 설정하여 주는 용도로 사용되어야 하며(publisher에서 subscription을 생성할 때 사용), 따라서 스케줄러로 다중스레딩 작업을 하는 경우 receive와 subscribe를 목적에 알맞게 사용해야 한다고 합니다.

receive(on:) & subscribe(on:) 참고 링크



Combine : Why?


RxSwift vs. Combine


Combine(1)에서 공부한 것은 publisher, subscriber, operator 이었는데요. Combine이 RxSwift와 비슷하다 해서 가져와 본 그림입니다.

Publisher - Observable
Subscriber - Observer
Operator - Operator

Rx의 역할이 각각 상통하는 것을 볼 수 있습니다.

하지만 세부적으로는 RxSwift와 다른 기능이 combine에 존재합니다. 그래야 Apple이 굳이 있는 RxSwift를 쓰지 않고 Combine을 만든 이유를 알 것입니다.

  • Generic
  • Request-driven
  • Async Event

제네릭하기 때문에 작성해야 하는 코드의 양을 줄일 수 있습니다. 이는 RxSwift와 비교해 보았을 때도 현저히 줄어들은 코드 양을 확인할 수 있다고 합니다.
또한 요청 기반이기 때문에 메모리 사용성 및 성능을 높일 수 있습니다. 비동기 이벤트들을 다루기 때문에 요청 기반이라는 점은 어쩌면 필수 사항이라고 생각합니다.
Combine은 잘 사용한다면 그 이상의 것을 만들 수도 있습니다. 여러 비동기 인터페이스들을 잘 조합하여 사용한다면 closure나 callback 등 까다로운 기술을 제거할 수 있기 때문에 코드의 유지보수가 쉬워질 것입니다.





Combine(1) 보러가기

좋은 웹페이지 즐겨찾기