Language/Swift

[Swift] Reactive Programming Combine - Resource

jaewpark 2024. 5. 29. 22:42

Resource를 관리할 수 있는 2가지 연산자가 있습니다.

  • share
  • multicast

share

upstream에서 받은 요소를 여러 subscibers에게 공유하는 Publisher.Share 클래스의 인스턴스입니다.

연산자는 반환한 publisher는 여러 subscribers를 지원합니다.

subscribers는 publisher로부터 변경되지 않은 요소와 완료 상태를 받습니다.

 

Publisher.Share는 사실상 Publishers.Multicast 와 PassthroughSubject의 조합으로 암시적으로 autoconnect이 포함되어 있습니다. 처음 들어오는 subscriber와 함께 한 번 구독합니다. upstream에서 받은 값을 첫 subscriber 및 이후 구독하는 subscribers에게 전달합니다.

 

share 연산자를 사용하여 두 명의 subscriber에게 각각 동일한 난수를 공유합니다.

연산자가 없으면 서로 다른 임의의 값을 수신합니다.

let pub = (1...3).publisher
    .delay(for: 1, scheduler: DispatchQueue.main)
    .map( { _ in return Int.random(in: 0...100) } )
    .print("Random")
    .share()


cancellable1 = pub
    .sink { print ("Stream 1 received: \($0)")}
cancellable2 = pub
    .sink { print ("Stream 2 received: \($0)")}

 

구독이 생성되었을 때, 이미 데이터 전송이 완료될 수 있습니다. 

 

multicast

downstream subscribers가 여러 명이지만 upstream publisher가 이벤트당 하나의 receive() 만 처리하도록 하려는 경우에 사용합니다. 이는 네트워크 요청 수행과 같이 중복하고 싶지 않은 값비싼 작업을 수행할 때 유용합니다.

 

PassthroughSubject 를 생성하는 multicast 연산자를 사용하여 값을 공유합니다.

ConnectablePublisher 이기에 connect()를 호출한 후에만 publsihing 시작됩니다.

let pub = ["First", "Second", "Third"].publisher
    .map( { return ($0, Int.random(in: 0...100)) } )
    .print("Random")
    .multicast { PassthroughSubject<(String, Int), Never>() }


cancellable1 = pub
   .sink { print ("Stream 1 received: \($0)")}
cancellable2 = pub
   .sink { print ("Stream 2 received: \($0)")}
   
pub.connect()

 

Future

단일 요소를 비동기적으로 publush 합니다.

Future.Promise를 취하는 클로저로 초기화하고 클로저는 성공 또는 실패를 나타내는 결과와 함께 promise 를 호출합니다.

 

Future는 클래스입니다. 

이행된 Promise의 결과를 current 및 future의 subscribers에게 전달합니다.

실제로 Future는 구독을 기다리지 않고 즉시 일부 작업을 수행하기 시작하면서 한 번만 작업을 수행하고 원하는 수의 subscribers에게 결과를 전달할 수 있습니다.

let future = Future<Int, Error> { fulfill in
  do {
    let result = try performSomeWork()
    fulfill(.success(result))
  } catch {
    fulfill(.failure(error))
  }
}

 

Future를 구독하지 않더라도 생성하면 클로저가 호출되어 작업이 수행됩니다. 

 

async-await 구문과 통합하기 위해 대기 중인 호출자에게 그 값을 제공할 수 있습니다.

func generateAsyncRandomNumberFromFuture() -> Future <Int, Never> {
    return Future() { promise in
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            let number = Int.random(in: 1...10)
            promise(Result.success(number))
        }
    }
}

let number = await generateAsyncRandomNumberFromFuture().value
print("Got random number \(number).")