Combine은 iOS 개발자들에게 데이터 스트림을 처리하고 비동기 이벤트를 관리하는데 매우 강력한 도구를 제공합니다. 특히 SwiftUI와 함께 사용하면 더욱 직관적이고 효율적인 코드 작성을 할 수 있습니다. 이 글에서는 Combine의 대표적인 오퍼레이터를 정리하고, 활용 예제와 함께 설명할 예정입니다. 여기서 데이터 흐름은 퍼블리셔와 서브스크라이버의 형태로 처리되는데 쉽게 말해, 어떤 데이터가 발생하면 그걸 듣고 처리할 수 있는 구조입니다.
대표적인 Combine 오퍼레이터 정리
Combine의 오퍼레이터는 데이터 변형, 필터링, 결합, 시간 기반 처리 등 다양한 기능을 제공합니다. 아래에서 SwiftUI와 함께 유용하게 사용할 수 있는 주요 오퍼레이터를 소개합니다.
1. 데이터 변형 오퍼레이터
1-1. map
map은 입력 데이터를 변환하여 새로운 값을 생성합니다. SwiftUI의 Text 필드 값 처리와 같은 간단한 작업에서 유용합니다. 예를 들어, 숫자를 받아서 10을 곱하거나, 문자열을 대문자로 바꾸는 작업할 때 사용됩니다.
import Combine
Just(2)
.map { $0 * 10 }
.sink { print($0) } // 출력: 20
1-2. flatMap
flatMap은 퍼블리셔가 반환하는 또 다른 퍼블리셔를 구독하고 그 값을 스트림에 반영합니다. 즉, 퍼블리셔 속의 퍼블리셔를 하나의 스트림으로 만들어준다고 생각할 수 있습니다.
Just(2)
.flatMap { Just($0 * 10) }
.sink { print($0) } // 출력: 20
2. 데이터 필터링 오퍼레이터
2-1 filter
filter는 조건에 맞는 값만 전달합니다. SwiftUI의 TextFiled 값 검증 등에 활용할 수 있습니다. 또한 예를 들어 짝수만 혹은 홀수만 출력하고 싶을 때 사용할 수 있습니다.
[1, 2, 3, 4].publisher
.filter { $0 % 2 == 0 }
.sink { print($0) } // 출력: 2, 4
2-2 removeDuplicates
이전 값과 동일한 값은 필터링하여 전달하지 않습니다. 즉 중복된 값들을 처리할 필요가 없을 때 사용하면 좋습니다.
[1, 2, 2, 3].publisher
.removeDuplicates()
.sink { print($0) } // 출력: 1, 2, 3
3. 데이터 결합 오퍼레이터
3-1. combineLatest
combineLatest는 두 퍼블리셔의 최신 값을 결합하여 전달합니다. SwiftUI에서 여러 상태를 동시에 처리할 때 유용합니다. 즉 두 퍼블리셔 중 하나라도 새로운 값을 내보내면 최신 상태로 다시 결합합니다.
import Combine
let pub1 = CurrentValueSubject<Int, Never>(1)
let pub2 = CurrentValueSubject<String, Never>("A")
pub1.combineLatest(pub2)
.sink { print($0) } // 출력: (1, "A")
pub1.send(2) // 출력: (2, "A")
pub2.send("B") // 출력: (2, "B")
3-2. zip
zip은 두 퍼블리셔의 값을 짝지어 한 번에 전달합니다. 두 스트림이 동시에 값을 생성할 때 주로 사용됩니다.
let pub1 = [1, 2, 3].publisher
let pub2 = ["A", "B", "C"].publisher
pub1.zip(pub2)
.sink { print($0) } // 출력: (1, "A"), (2, "B"), (3, "C")
두 퍼블리셔의 순서를 엄격히 맞춰서 데이터를 다루고 싶을 때 사용합니다.
3-5. merge
merge는 여러 퍼블리셔에서 나오는 데이터를 순서에 상관없이 하나로 합쳐줍니다. 단 입력되는 순서대로 처리합니다.
let pub1 = [1, 2].publisher
let pub2 = [3, 4].publisher
pub1.merge(with: pub2)
.sink { print($0) } // 출력: 1, 2, 3, 4
4. 시간과 관련된 오퍼레이터
4-1. debounce
debounce는 일정 시간 동안 변동이 없을 때만 값을 전달합니다. 사용자 입력 처리에서 과도한 이벤트 호출을 막을 수 있습니다.
import Combine
let subject = PassthroughSubject<String, Never>()
subject.debounce(for: .seconds(1), scheduler: RunLoop.main)
.sink { print($0) }
subject.send("Hello") // 입력 후 기다리지 않으면 출력되지 않음.
subject.send("Hello, World!") // 1초 후에 "Hello, World!" 출력
4-2 throttle
throttle은 데이터를 일정 시간 간격으로만 전달합니다. 가장 최신 값만 보낼지, 아니면 첫 번째 값만 보낼지 선택할 수 있습니다.
let subject = PassthroughSubject<String, Never>()
subject.throttle(for: .seconds(2), scheduler: RunLoop.main, latest: true)
.sink { print($0) }
subject.send("A")
subject.send("B") // 2초 후 "B" 출력