Swift Combine sink: 비동기 이벤트 처리의 핵심

작성일 :

Swift Combine sink: 비동기 이벤트 처리의 핵심

Swift Combine은 애플의 리액티브 프로그래밍 프레임워크로, 비동기 이벤트 처리와 데이터 스트림 관리에 탁월한 성능을 발휘합니다. Combine의 핵심 요소 중 하나인 sink는 Publisher로부터 전달된 데이터를 처리하는데 매우 중요한 역할을 합니다. 이 글에서는 sink의 개념과 사용법, 그리고 다양한 활용 예제를 통해 sink가 비동기 이벤트 처리에서 왜 중요한지를 자세히 살펴보겠습니다.

sink란 무엇인가?

sink는 Combine에서 가장 많이 사용되는 연산자 중 하나로, Publisher로부터 발행된 값을 수신하고 처리하는 Subscriber를 생성합니다. sink는 클로저를 통해 값을 처리하고, 필요한 경우 오류를 처리할 수 있습니다. 이를 통해 비동기 데이터 스트림을 간단하고 명확하게 처리할 수 있습니다.

sink의 기본 사용법

sink를 사용하려면 먼저 Publisher를 생성하고, 그 Publisher에 sink를 적용하여 Subscriber를 생성해야 합니다. 기본적인 사용법은 다음과 같습니다:

swift
import Combine

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

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

이 예제에서 Just Publisher는 "Hello, Combine!" 문자열을 발행하며, sink를 통해 이 값을 수신하고 출력합니다. 완료 이벤트가 발생하면 "Finished"를 출력합니다.

비동기 이벤트 처리

Combine의 주요 목적 중 하나는 비동기 이벤트를 처리하는 것입니다. sink는 이러한 비동기 이벤트를 처리하는 데 매우 유용합니다. 비동기 작업의 예로 네트워크 요청을 생각해볼 수 있습니다.

네트워크 요청 처리

아래 예제는 URLSession을 사용하여 네트워크 요청을 보내고, sink를 통해 응답 데이터를 처리하는 방법을 보여줍니다:

swift
import Combine

guard let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1") else {
    fatalError("Invalid URL")
}

let cancellable = URLSession.shared.dataTaskPublisher(for: url)
    .map { $0.data }
    .decode(type: Todo.self, decoder: JSONDecoder())
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("Request finished")
        case .failure(let error):
            print("Request failed with error: \(error)")
        }
    }, receiveValue: { todo in
        print("Received todo: \(todo)")
    })

struct Todo: Codable {
    let id: Int
    let title: String
    let completed: Bool
}

이 예제에서 URLSession.shared.dataTaskPublisher는 지정된 URL로 네트워크 요청을 보내고, 응답 데이터를 mapdecode 연산자를 사용하여 가공합니다. sink는 가공된 데이터를 수신하고 출력하며, 완료 이벤트와 오류를 처리합니다.

사용자 인터페이스 이벤트 처리

Combine과 sink를 사용하여 사용자 인터페이스(UI) 이벤트도 처리할 수 있습니다. 예를 들어, 버튼 클릭 이벤트를 처리하는 경우를 살펴보겠습니다.

swift
import Combine
import UIKit

// 버튼 생성
let button = UIButton()
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가 값을 발행하고, sink를 통해 이를 처리합니다.

데이터 변환 및 처리

sink는 단순히 값을 수신하는 것 외에도, 데이터 변환 및 처리에 유용합니다. Combine의 다양한 연산자와 결합하여 데이터를 가공할 수 있습니다.

데이터 변환 예제

아래 예제는 숫자 배열을 문자열 배열로 변환하여 처리하는 방법을 보여줍니다:

swift
import Combine

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

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

이 예제에서 숫자 배열을 발행하는 Publisher에 map 연산자를 사용하여 숫자를 문자열로 변환하고, sink를 통해 변환된 값을 수신하고 출력합니다.

오류 처리

Combine을 사용한 비동기 작업에서는 오류 처리가 매우 중요합니다. sink를 사용하면 오류를 간단하게 처리할 수 있습니다.

오류 처리 예제

아래 예제는 네트워크 요청에서 발생할 수 있는 오류를 처리하는 방법을 보여줍니다:

swift
import Combine

let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
let cancellable = URLSession.shared.dataTaskPublisher(for: url)
    .tryMap { data, response -> Data in
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }
    .decode(type: Todo.self, decoder: JSONDecoder())
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("Request finished")
        case .failure(let error):
            print("Request failed with error: \(error)")
        }
    }, receiveValue: { todo in
        print("Received todo: \(todo)")
    })

struct Todo: Codable {
    let id: Int
    let title: String
    let completed: Bool
}

이 예제에서 tryMap 연산자를 사용하여 HTTP 응답의 상태 코드를 검사하고, 상태 코드가 200이 아닌 경우 오류를 발생시킵니다. sink는 오류를 처리하고 적절한 메시지를 출력합니다.

취소 가능성

Combine에서 생성된 모든 sinkAnyCancellable 객체를 반환합니다. 이를 통해 비동기 작업을 언제든지 취소할 수 있습니다.

취소 예제

아래 예제는 타이머를 사용하여 주기적으로 값을 발행하고, 일정 시간 후에 구독을 취소하는 방법을 보여줍니다:

swift
import Combine

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

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

// 5초 후에 구독 취소
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    cancellable.cancel()
    print("Subscription cancelled")
}

이 예제에서 타이머는 1초마다 현재 시간을 발행하며, 5초 후에 구독을 취소합니다.

결론

Combine의 sink는 비동기 이벤트 처리의 핵심 도구로, Publisher로부터 전달된 데이터를 간단하고 효과적으로 처리할 수 있습니다. sink를 사용하면 네트워크 요청, 사용자 인터페이스 이벤트, 데이터 변환 및 오류 처리 등을 손쉽게 구현할 수 있습니다. sink의 강력한 기능을 활용하여 더욱 안정적이고 효율적인 비동기 로직을 구현해 보세요. Combine과 sink를 통해 비동기 프로그래밍의 복잡성을 줄이고, 더욱 직관적인 코드 작성을 할 수 있을 것입니다.