Swift Combine Subject의 이해와 활용법

작성일 :

다양한 Swift Combine Publisher 사용 예제

Swift Combine은 애플의 리액티브 프로그래밍 프레임워크로, 비동기 이벤트 처리와 데이터 스트림 관리를 위해 설계되었습니다. Combine은 Publisher와 Subscriber로 구성되며, Publisher는 데이터를 발행하는 역할을 합니다. 이 글에서는 다양한 Swift Combine Publisher와 그 사용 예제에 대해 알아보겠습니다.

기본 Publisher 타입

Combine 프레임워크는 다양한 내장 Publisher 타입을 제공하여 다양한 상황에 맞게 사용할 수 있습니다. 주요 Publisher 타입과 그 사용 예제를 살펴보겠습니다.

Just

Just는 단 하나의 값을 발행하는 Publisher입니다. 오류를 발생시키지 않으며, 매우 간단한 형태의 Publisher입니다.

swift
import Combine

let justPublisher = Just("Hello, Combine!")

let cancellable = justPublisher.sink(receiveCompletion: { completion in
    switch completion {
    case .finished:
        print("Finished")
    case .failure(let error):
        print("Error: \(error)")
    }
}, receiveValue: { value in
    print("Received value: \(value)")
})

이 예제는 "Hello, Combine!" 문자열을 발행하고 완료를 출력합니다.

Future

Future는 비동기 작업의 결과를 발행하는 Publisher입니다. 비동기 작업이 완료되었을 때, 결과 값을 한 번 발행하고 종료합니다.

swift
let futurePublisher = Future<String, Error> { promise in
    DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
        promise(.success("Async result"))
    }
}

let cancellable = futurePublisher.sink(receiveCompletion: { completion in
    switch completion {
    case .finished:
        print("Finished")
    case .failure(let error):
        print("Error: \(error)")
    }
}, receiveValue: { value in
    print("Received value: \(value)")
})

이 예제는 비동기 작업이 완료된 후 "Async result"를 발행합니다.

Deferred

Deferred는 Subscriber가 구독할 때까지 Publisher의 생성 및 실행을 지연시키는 역할을 합니다. 이를 통해 구독 시점에 Publisher를 동적으로 생성할 수 있습니다.

swift
let deferredPublisher = Deferred {
    Just(Date())
}

let cancellable = deferredPublisher.sink(receiveValue: { value in
    print("Received date: \(value)")
})

이 예제는 구독 시점에 현재 날짜와 시간을 발행합니다.

Empty

Empty는 즉시 완료되거나 오류를 발생시키지 않고 아무 값도 발행하지 않는 Publisher입니다.

swift
let emptyPublisher = Empty<Int, Never>()

let cancellable = emptyPublisher.sink(receiveCompletion: { completion in
    switch completion {
    case .finished:
        print("Finished")
    case .failure:
        print("Error")
    }
}, receiveValue: { value in
    print("Received value: \(value)")
})

이 예제는 아무 값도 발행하지 않고 완료됩니다.

Fail

Fail는 특정 오류를 즉시 발행하고 완료되는 Publisher입니다.

swift
enum MyError: Error {
    case exampleError
}

let failPublisher = Fail<Int, MyError>(error: .exampleError)

let cancellable = failPublisher.sink(receiveCompletion: { completion in
    switch completion {
    case .finished:
        print("Finished")
    case .failure(let error):
        print("Error: \(error)")
    }
}, receiveValue: { value in
    print("Received value: \(value)")
})

이 예제는 오류를 발행하고 종료됩니다.

Sequence를 Publisher로 사용하기

Combine은 배열과 같은 Sequence 타입을 Publisher로 변환할 수 있습니다.

swift
let numbers = [1, 2, 3, 4, 5].publisher

let cancellable = numbers.sink(receiveCompletion: { completion in
    switch completion {
    case .finished:
        print("Finished")
    case .failure(let error):
        print("Error: \(error)")
    }
}, receiveValue: { value in
    print("Received number: \(value)")
})

이 예제는 배열의 각 요소를 순차적으로 발행하고 완료됩니다.

Timer Publisher

Combine은 주기적으로 값을 발행하는 Timer Publisher도 제공합니다.

swift
let timerPublisher = Timer.publish(every: 1.0, on: .main, in: .default).autoconnect()

let cancellable = timerPublisher.sink(receiveValue: { value in
    print("Timer fired at: \(value)")
})

이 예제는 1초마다 현재 시간을 발행합니다.

NotificationCenter Publisher

NotificationCenter에서 게시된 알림을 Publisher로 사용할 수도 있습니다.

swift
let notificationPublisher = NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)

let cancellable = notificationPublisher.sink(receiveValue: { notification in
    print("Received notification: \(notification)")
})

이 예제는 앱이 활성화될 때마다 알림을 발행합니다.

URLSession Data Task Publisher

Combine을 사용하여 네트워크 요청도 처리할 수 있습니다.

swift
let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
let dataTaskPublisher = URLSession.shared.dataTaskPublisher(for: url)

let cancellable = dataTaskPublisher.sink(receiveCompletion: { completion in
    switch completion {
    case .finished:
        print("Finished")
    case .failure(let error):
        print("Error: \(error)")
    }
}, receiveValue: { data, response in# Swift Combine Subject의 이해와 활용법

Swift Combine은 애플의 리액티브 프로그래밍 프레임워크로, 비동기 이벤트 처리와 데이터 스트림 관리를 위해 설계되었습니다. Combine의 핵심 구성 요소 중 하나인 SubjectPublisherSubscriber의 역할을 동시에 수행할 수 있는 특별한 객체입니다. 이 글에서는 Swift CombineSubject에 대한 기본 개념과 다양한 활용법에 대해 알아보겠습니다.

## Subject란 무엇인가?

SubjectCombine에서 매우 유용한 구성 요소로, Publisher처럼 데이터를 발행할 수 있고 Subscriber처럼 다른 Publisher로부터 데이터를 받을 수 있습니다. 따라서 Subject는 데이터 스트림을 제어하거나 중간에서 데이터를 가공하는 데 유용합니다. Combine 프레임워크는 두 가지 주요 유형의 Subject를 제공합니다:

1. **PassthroughSubject**: 초기값 없이 새로운 값을 발행하는 Subject입니다.
2. **CurrentValueSubject**: 초기값을 가지며 최신 값을 유지하는 Subject입니다.

## PassthroughSubject

### 기본 개념

PassthroughSubject는 초기값이 없으며, 새로운 값을 발행하는 역할을 합니다. 값을 발행하기 전까지는 아무 데이터도 가지고 있지 않습니다. 따라서 주로 이벤트를 전달하는 데 사용됩니다.

### 사용 예제

PassthroughSubject의 기본 사용법을 살펴보겠습니다.

```swift
import Combine

// PassthroughSubject 생성
let subject = PassthroughSubject<String, Never>()

// Subscriber 생성 및 구독
let cancellable = subject.sink(receiveCompletion: { completion in
    switch completion {
    case .finished:
        print("Finished")
    case .failure(let error):
        print("Error: \(error)")
    }
}, receiveValue: { value in
    print("Received value: \(value)")
})

// 값 발행
subject.send("Hello")
subject.send("Combine")
subject.send(completion: .finished)

이 예제에서 PassthroughSubject는 "Hello"와 "Combine" 문자열을 발행하고, 발행된 값은 Subscriber에 의해 출력됩니다. 마지막으로 완료 이벤트를 발행하여 스트림이 종료되었음을 알립니다.

이벤트 처리

PassthroughSubject는 주로 사용자 입력 이벤트, 네트워크 응답 등과 같은 일회성 이벤트를 처리하는 데 유용합니다. 예를 들어, 버튼 클릭 이벤트를 처리하는 경우를 생각해볼 수 있습니다.

swift
import Combine
import UIKit

// 버튼 생성
let button = UIButton()

// PassthroughSubject 생성
let buttonTapSubject = PassthroughSubject<Void, Never>()

// 버튼 클릭 이벤트를 PassthroughSubject로 전달
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)

@objc func buttonTapped() {
    buttonTapSubject.send()
}

// Subscriber 생성 및 구독
let cancellable = buttonTapSubject.sink {
    print("Button was tapped!")
}

이 예제에서 버튼 클릭 이벤트가 발생할 때마다 buttonTapSubject가 값을 발행하고, Subscriber는 이를 받아서 처리합니다.

CurrentValueSubject

기본 개념

CurrentValueSubject는 초기값을 가지며, 최신 값을 유지하는 역할을 합니다. 새로운 값을 발행할 때마다 최신 값이 업데이트되며, Subscriber가 구독할 때 현재 저장된 값을 즉시 받을 수 있습니다.

사용 예제

CurrentValueSubject의 기본 사용법을 살펴보겠습니다.

swift
import Combine

// CurrentValueSubject 생성 (초기값 설정)
let subject = CurrentValueSubject<String, Never>("Initial value")

// Subscriber 생성 및 구독
let cancellable = subject.sink(receiveCompletion: { completion in
    switch completion {
    case .finished:
        print("Finished")
    case .failure(let error):
        print("Error: \(error)")
    }
}, receiveValue: { value in
    print("Received value: \(value)")
})

// 값 발행
subject.send("Updated value")
subject.send("Another update")
subject.send(completion: .finished)

이 예제에서 CurrentValueSubject는 "Initial value"로 초기화되며, 새로운 값이 발행될 때마다 최신 값이 업데이트됩니다. 구독자는 항상 최신 값을 받을 수 있습니다.

상태 관리

CurrentValueSubject는 주로 상태 관리를 위해 사용됩니다. 예를 들어, ViewModel에서 UI 상태를 관리하는 경우를 생각해볼 수 있습니다.

swift
import Combine

class ViewModel {
    // CurrentValueSubject를 사용하여 상태 관리
    var state = CurrentValueSubject<String, Never>("Initial state")

    func updateState(newState: String) {
        state.send(newState)
    }
}

// ViewModel 인스턴스 생성
let viewModel = ViewModel()

// Subscriber 생성 및 구독
let cancellable = viewModel.state.sink { value in
    print("State updated to: \(value)")
}

// 상태 업데이트
viewModel.updateState(newState: "Loading")
viewModel.updateState(newState: "Loaded")

이 예제에서 ViewModelCurrentValueSubject를 사용하여 UI 상태를 관리하며, 상태가 업데이트될 때마다 Subscriber에게 최신 상태를 전달합니다.

PassthroughSubject와 CurrentValueSubject 비교

PassthroughSubject와 CurrentValueSubject는 각각 고유한 용도와 특성을 가지고 있습니다. PassthroughSubject는 주로 일회성 이벤트를 처리하는 데 사용되며, CurrentValueSubject는 최신 상태를 관리하는 데 적합합니다. 두 Subject를 적절히 활용하면 다양한 비동기 데이터 스트림을 효과적으로 처리할 수 있습니다.

사용 사례 요약

  • PassthroughSubject:

    • 초기값이 필요 없는 경우
    • 일회성 이벤트(예: 사용자 입력, 네트워크 응답) 처리
    • 값을 발행하기 전까지는 아무 데이터도 보유하지 않음
  • CurrentValueSubject:

    • 초기값이 필요한 경우
    • 상태 관리(예: UI 상태) 및 최신 값 유지
    • 구독 시점에 최신 값을 즉시 전달

결론

Swift Combine의 Subject는 비동기 데이터 스트림을 제어하고 관리하는 데 매우 유용한 도구입니다. PassthroughSubject와 CurrentValueSubject를 적절히 활용하면 다양한 비동기 이벤트와 상태 변화를 효율적으로 처리할 수 있습니다. Combine을 사용하여 복잡한 비동기 로직을 간단하고 명확하게 구현할 수 있으며, 이를 통해 더 나은 사용자 경험을 제공할 수 있습니다. Subject의 기본 개념과 사용법을 이해하고 다양한 예제를 통해 익숙해지면, 리액티브 프로그래밍의 강력한 기능을 최대한 활용할 수 있을 것입니다.