Swift Combine Subject로 데이터 스트림 제어하기

작성일 :

Swift Combine Subject로 데이터 스트림 제어하기

Swift Combine은 애플의 리액티브 프로그래밍 프레임워크로, 비동기 이벤트 처리와 데이터 스트림 관리를 위한 강력한 도구를 제공합니다. 이 중에서 Subject는 Publisher와 Subscriber의 역할을 동시에 수행할 수 있는 특별한 객체로, 데이터 스트림을 제어하는 데 매우 유용합니다. 이 글에서는 Swift Combine Subject를 활용하여 데이터 스트림을 제어하는 방법을 다양한 예제를 통해 자세히 살펴보겠습니다.

Subject란 무엇인가?

Subject는 Combine에서 데이터를 발행(Publisher)하고, 다른 Publisher로부터 데이터를 구독(Subscriber)할 수 있는 객체입니다. 이를 통해 데이터 스트림을 생성, 제어 및 가공할 수 있습니다. 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" 문자열을 발행하고, 이를 구독자에게 전달합니다. 마지막으로 완료 이벤트를 발행하여 스트림이 종료되었음을 알립니다.

사용자 이벤트 처리

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가 값을 발행하고, 이를 구독자에게 전달합니다. 이를 통해 버튼 클릭 이벤트를 간단하게 처리할 수 있습니다.

CurrentValueSubject

기본 개념

CurrentValueSubject는 초기값을 가지며, 최신 값을 유지합니다. 새로운 값을 발행할 때마다 최신 값이 업데이트되며, 구독자가 구독할 때 현재 저장된 값을 즉시 받을 수 있습니다. 주로 상태 관리에 사용됩니다.

사용 예제

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 상태를 관리하며, 상태가 업데이트될 때마다 구독자에게 최신 상태를 전달합니다.

PassthroughSubject와 CurrentValueSubject 비교

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

사용 사례 요약

  • PassthroughSubject:

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

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

Subject로 데이터 스트림 제어하기

Subject는 데이터 스트림을 제어하는 데 매우 유용합니다. 여러 Publisher를 결합하거나 데이터를 가공하여 구독자에게 전달할 수 있습니다. 몇 가지 응용 예제를 통해 이를 살펴보겠습니다.

여러 Publisher 결합

Subject를 사용하여 여러 Publisher의 데이터를 결합할 수 있습니다.

swift
import Combine

let subject1 = PassthroughSubject<Int, Never>()
let subject2 = PassthroughSubject<String, Never>()

let cancellable = subject1.combineLatest(subject2)
    .sink { int, string in
        print("Received \(int) and \(string)")
    }

subject1.send(1)
subject2.send("A")
subject1.send(2)
subject2.send("B")

이 예제에서 subject1subject2는 각각 정수와 문자열을 발행하며, combineLatest 연산자를 사용하여 두 Subject의 최신 값을 결합하여 구독자에게 전달합니다.

데이터 가공

Subject를 사용하여 데이터를 가공하고 구독자에게 전달할 수 있습니다.

swift
import Combine

let subject = PassthroughSubject<Int, Never>()

let cancellable = subject
    .map { $0 * 2 }
    .sink { value in
        print("Received value: \(value)")
    }

subject.send(1)
subject.send(2)
subject.send(3)

이 예제에서 subject는 정수를 발행하며, map 연산자를 사용하여 발행된 값을 두 배로 변환하여 구독자에게 전달합니다.

결론

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