Taehyeon-Kim / Taehyeon-Kim.github.io

The life of recording is unbreakable
https://taehyeon-kim.github.io
MIT License
1 stars 0 forks source link

Combine101: share(), multicast(), connect(), autoconnect() #7

Open Taehyeon-Kim opened 10 months ago

Taehyeon-Kim commented 10 months ago

문제 의식

share()
  1. 비용이 많이 드는 작업에서 중복 작업을 제거하고 싶다.
  2. struct로 디자인된 Combine에서 리소스를 공유할 수 있도록 참조를 사용하고 싶다.
multicast()
  1. share는 버퍼링 시스템이 별도로 존재하지 않는다.
  2. 모든 구독이 완료되면 이벤트를 방출하고 싶다.

Combine

import Foundation
import Combine

let numbers = [1,2,3,4,5,6,7,8,9,10]
  .publisher
  .print()

var cancellabes = Set<AnyCancellable>()

numbers.sink { _ in }.store(in: &cancellabes)
numbers.sink { _ in }.store(in: &cancellabes)
receive subscription: ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
request unlimited
receive value: (1)
receive value: (2)
receive value: (3)
receive value: (4)
receive value: (5)
receive value: (6)
receive value: (7)
receive value: (8)
receive value: (9)
receive value: (10)
receive finished
receive subscription: ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
request unlimited
receive value: (1)
receive value: (2)
receive value: (3)
receive value: (4)
receive value: (5)
receive value: (6)
receive value: (7)
receive value: (8)
receive value: (9)
receive value: (10)
receive finished

share operator

struct Post: Decodable {
  let userId: Int
  let id: Int
  let title: String
  // let body: String
}

let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let posts = URLSession.shared.dataTaskPublisher(for: url)
  .map(\.data)
  .decode(type: [Post].self, decoder: JSONDecoder())
  .replaceError(with: [])
  .print()
  .share()

posts
  .sink(receiveValue: {
    print("subscription1 value: \($0)") })
  .store(in: &cancellables)
posts
  .sink(receiveValue: {
    print("subscription2 value: \($0)") })
  .store(in: &cancellables)

image

multicast operator

image

let postsSubject = PassthroughSubject<[Post], Never>()

let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let posts = URLSession.shared.dataTaskPublisher(for: url)
  .map(\.data)
  .decode(type: [Post].self, decoder: JSONDecoder())
  .replaceError(with: [])
  .print()
  .multicast(subject: postsSubject)
posts.connect().store(in: &cancellables)

Advantages

makeConnectable()

해당 메서드를 사용하면 연결 가능한 래퍼를 생성할 수 있다. Subject를 다룰 필요 없이 즉시 퍼블리셔를 ConnectablePublisher로 전환할 수 있다.

let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let posts = URLSession.shared.dataTaskPublisher(for: url)
  .map(\.data)
  .decode(type: [Post].self, decoder: JSONDecoder())
  .replaceError(with: [])
  .print()
  .makeConnectable()

posts.connect().store(in: &cancellables)

autoconnect operator

let cancellable = Timer.publish(every: 1, on: .main, in: .default)
    .autoconnect()
    .sink() { date in
        print ("Date now: \(date)")
     }

정리

  1. 콤바인에서는 퍼블리셔가 기본적으로 Struct임
  2. Value Type이라 구독이 발생하면 퍼블리셔의 복사가 발생함
  3. 이러면 API 통신 같은 거에서 중복 요청이 발생할 수 있음
  4. 여러 구독자가 생겨도 1회만 요청하게 바꾸고 싶음
  5. share, multicast 연산자
  6. → Struct를 Class로 래핑함 (Reference Type)
  7. 여러 구독자가 1회의 요청에서 얻은 데이터를 받을 수 있게 해줌
  8. share는 구독이 발생하면 곧바로 실행됨
    1. 구독자를 여러개 만들고 있는 과정에서 도중에 실행되면 나중에 구독한 친구는 값을 못받을 수 있음
    2. upstream을 여러 개의 publisher로 나누고 싶을 때.
  9. multicast
    1. 구독자들을 미리 다 만들고 개발자가 필요한 시점에 connect()를 실행하면 그 때 퍼블리셔가 값을 생산하기 시작함
    2. ConnectablePublisher인데 애플에서 기본적으로 제공하는 Timer 퍼블리셔가 이 타입임
    3. 근데 우리가 모든 상황에서 여러 구독자를 만드는 것이 아니기 때문에 ConnectablePublisher 이지만 곧바로 실행되길 원하는 상황이 생길 수 있음 (Timer) 이 때를 위한 오퍼레이터가 autoconnect()이다.
  10. 반드시 share를 사용해야 하는 것은 아님. Future를 사용해도 동일한 결과를 얻어낼 수 있음

Links: