[Swift/Combine] 다양한 Publisher의 종류 정리

이정훈·2025년 1월 2일
0

Combine Framework

목록 보기
3/6
post-thumbnail

이번 포스트에서는 Combine 프레임워크에서 생성할 수 있는 다양한 형태의 Publisher를 알아보려고 한다.

Just

Just는 가장 단순한 형태의 Publisher로 하나의 값을 방출하고 종료된다. 또한 에러를 방출하지 않기 때문에 Just의 Error 타입은 Never이다.

import Combine

Just(1)
    .sink { completion in
        print(completion)
    } receiveValue: { value in
        print(value)
    }
    
// 1
// finished

Future

Future는 의미 그대로 미래에 생성되는 값 또는 에러를 방출하는 Publisher이다. 미래에 생성되는 값의 예로 네트워크 통신의 결과 등을 예로 들 수 있다. 레거시 형태의 Completion Handler를 Combine Framework에 도입하는 경우 결합하여 사용할 수 있다.

Just와 마찬가지로 promise를 통해 하나의 값을 전달한 뒤 바로 종료된다.

import Combine
import Foundation

func generateAsyncRandomNumber() -> AnyPublisher<Int, Never> {
    return Future { promise in
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            let randomNumber = Int.random(in: 1..<100)
            promise(.success(randomNumber))
        }
    }
    .eraseToAnyPublisher()
}

let cancellable = generateAsyncRandomNumber()
    .sink { completion in
        switch completion {
        case .finished:
            print("finish")
        case .failure(let error):
            print("error: \(error)")
        }
    } receiveValue: { value in
        print(value)
    }
    
//89
//finish

Fail

Fail은 에러를 발생시키고 바로 종료되는 Publisher이다.

import Combine

enum SomeError: Error {
    case someError
}

Fail(error: SomeError.someError)
    .sink {
        print($0)
    } receiveValue: { value in
        print(value)
    }
    
//failure(__lldb_expr_18.SomeError.someError)

Empty

아무 값도 방출하지 않는 Publisher이다. Empty 생성 시 즉시 종료할지 옵션으로 선택할 수 있다.

import Combine

Empty<Int, Error>(completeImmediately: true)
    .sink(receiveCompletion: {
        print($0)
    }, receiveValue: {
        //아무것도 받지 않음
        print($0)
    })
    
//finished

Deferred

Deferred는 구독 전까지 기다렸다가 구독이 발생하면 Publisher를 반환한다. 다시말해, Deffered 생성자로 전달되는 클로저는 구독이 발생하면 Publisher를 반환하고 이 Publisher는 값을 지연 방출한다.

import Combine

let publisher = Deferred { Just(0) }

publisher
    .sink(receiveCompletion: {
            print($0)
        }, receiveValue: { value in
            print(value)
        })
        
//0
//finished        

Subject

Subject는 일종의 Publisher이다. 따라서 아래의 protocol 선언에서도 Publisher를 따르고 있는 모습을 확인할 수 있다.

protocol Subject<Output, Failure> : AnyObject, Publisher

그럼 Subject가 위에서 살펴본 Convenience Publisher와 다른 점은 무엇일까? Subject의 특징은 외부에서 값을 데이터 스트림에 주입할 수 있는 데에 있다. 다시 말해 외부에서 주입한 값을 방출하는 Publisher라고 할 수 있다.

이때 외부에서 값을 주입하기 위해 send(_:) 메서드를 사용하여 값을 전달하고, Subject는 해당 값을 방출할 수 있다.

지금부터 Subject protocol을 따르는 두 가지의 Subject를 알아보자.

CurrentValueSubject

CurrentValueSubject단일 값을 래핑하고 값이 변경될 때마다 값을 방출하는 Subject이다. CurrentValueSubject버퍼를 가지기 때문에 구독이 발생하면 해당 버퍼의 단일 값을 방출한다. 또한 버퍼에는 항상 값이 존재해야 하기 때문에 인스턴스 생성 시 초기 값을 전달한다.

import Combine

let subject: CurrentValueSubject<Int, Never> = .init(1)
let cancellable: AnyCancellable = subject.sink(receiveValue: {
    print($0)
})

subject.send(2)
subject.send(completion: .finished)
subject.send(3)    // 스트림이 종료되었기 때문에 방출 안 함

// 1
// 2

위의 코드와 같이 구독 시작과 함께 초기 값이 방출되고, 외부에서 2라는 값을 주입하면, CurrentValueSubject는 해당 값을 방출한다. 하지만 스트림이 완료된 후에는 추가적인 값(3)을 보내더라도 더 이상 방출되지 않는다.

PassthroughSubject

PassthroughSubject는 초기값이 없이 외부에서 스트림에 값을 주입하면, 해당 값을 다운스트림에 전달하는 Subject이다. CurrentValueSubject과 달리 PassthroughSubject는 버퍼가 존재하지 않기 때문에 초기 값이 없다.

import Combine

let subject: PassthroughSubject<Int, Never> = .init()
let cancellables: AnyCancellable = subject.sink {
    print($0)
}

subject.send(1)
subject.send(2)
subject.send(completion: .finished)
subject.send(3)

// 1
// 2

위의 코드와 같이 구독을 시작한 후 외부에서 전달한 값을 방출하는 것을 확인할 수 있다. 또한 완료 신호를 전달하면 구독이 취소되므로 이후에 전달한 값은 출력되지 않는 것을 확인할 수 있다.

profile
새롭게 알게된 것을 기록하는 공간

0개의 댓글