Language/Swift
[Swift] Reactive Programming Combine - 5: Combining Operators
jaewpark
2024. 5. 11. 13:18
Combining Operators
prepend
... synctax를 사용하여 다양한 값(원래 publisher와 동일한 유형)의 목록을 받습니다.
let publisher = [3, 4].publisher
publisher
.prepend(1, 2)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
// *--- RESULUT ---*
// 1
// 2
// 3
// 4
prepend 연산자가 체인 연결을 하는 것은 접두사로 추가한다는 의미입니다.
그렇기에 연산자의 순서가 중요합니다.
let publisher = [3, 4].publisher
publisher
.prepend(1, 2)
.prepend(3, 4)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
// *--- RESULUT ---*
// 3
// 4
// 1
// 2
// 3
// 4
시퀀스에 부합하는 객체를 받을 수도 있습니다.
let publisher = [3, 4].publisher
publisher
.prepend([1, 2])
.prepend(Set(0...2))
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
두 개의 publisher를 연결할 수도 있습니다.
let publisher1 = [3, 4].publisher
let publisher2 = [103, 104].publisher
publisher2
.prepend(publisher1)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
// *--- RESULT ---*
// 3
// 4
// 103
// 104
let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = [103, 104].publisher
publisher2
.prepend(publisher1)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
publisher1.send(3)
publisher1.send(4)
publisher1.send(completion: .finished) // 완료를 하지 않으면 publisher2의 값을 내보내지 않음
append
prepend와는 반대로 뒤에 값을 연결합니다.
또한, 앞선 publisher가 완료되어야 값을 내보냅니다.
let publisher1 = [1, 2].publisher
let publisher2 = [3, 4].publisher
publisher1
.append(publisher2)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
// *--- RESULT ---*
// 1
// 2
// 3
// 4
let publisher = PassthroughSubject<Int, Never>()
publisher
.append(3, 4)
.append(5)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
publisher.send(1)
publisher.send(2)
// *--- RESULT ---*
// 1
// 2
let publisher = [1, 2, 3].publisher
publisher
.append([4, 5])
.append(stride(from: 6, to: 11, by: 3))
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
switchToLatest
보류 중인 publisher 구독을 취소하면서 최신 구독으로 전환할 수 있습니다.
let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<Int, Never>()
let publisher3 = PassthroughSubject<Int, Never>()
let publishers = PassthroughSubject<PassthroughSubject<Int, Never>, Never>()
publishers
.switchToLatest()
.sink(receiveCompletion: { _ in print("Completed!") },
receiveValue: { print($0) })
.store(in: &subscriptions)
publishers.send(publisher1)
publisher1.send(1)
publisher1.send(2)
publishers.send(publisher2)
publisher1.send(3)
publisher2.send(4)
publisher2.send(5)
publishers.send(publisher3)
publisher2.send(6)
publisher3.send(7)
publisher3.send(8)
publisher3.send(9)
publisher3.send(completion: .finished)
publishers.send(completion: .finished)
// *--- RESULT ---*
// 1
// 2
// 4
// 5
// 7
// 8
// 9
// Completed!
네트워크 요청을 트리거할 때 보류 중인 요청을 없애고 최신의 요청 값만 사용한다면 switchToLastest를 사용할 수 있습니다.
3초 뒤에 트리거되고 3.1초 트리거가 되면서 3초에 보낸 요청을 취소하는 것을 알 수 있습니다.
let url = URL(string: "https://source.unsplash.com/random")!
func getImage() -> AnyPublisher<UIImage?, Never> {
return URLSession.shared
.dataTaskPublisher(for: url)
.map { data, _ in UIImage(data: data) }
.print("image")
.replaceError(with: nil)
.eraseToAnyPublisher()
}
let taps = PassthroughSubject<Void, Never>()
taps
.map { _ in getImage() }
.switchToLatest()
.sink(receiveValue: { _ in })
.store(in: &subscriptions)
taps.send()
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
taps.send()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3.1) {
taps.send()
}
merge
다른 publisher와 결합할 수 있습니다.
let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<Int, Never>()
publisher1
.merge(with: publisher2)
.sink(receiveCompletion: { _ in print("Completed") },
receiveValue: { print($0) })
.store(in: &subscriptions)
publisher1.send(1)
publisher1.send(2)
publisher2.send(3)
publisher1.send(4)
publisher2.send(5)
combineLatest
서로 다른 값 유형의 publisher와 결합할 수 있습니다.
하지만 모든 publisher가 값을 내보낼 때마다 최신 값이 포함된 튜플 형식의 값을 내보냅니다.
let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<String, Never>()
publisher1
.combineLatest(publisher2)
.sink(receiveCompletion: { _ in print("Completed") },
receiveValue: { print("P1: \($0), P2: \($1)") })
.store(in: &subscriptions)
publisher1.send(1)
publisher1.send(2)
publisher2.send("a")
publisher2.send("b")
publisher1.send(3)
publisher2.send("c")
publisher1.send(completion: .finished)
publisher2.send("d")
publisher2.send(completion: .finished)
// *--- RESULT ---*
// P1: 2, P2: a
// P1: 2, P2: b
// P1: 3, P2: b
// P1: 3, P2: c
// P1: 3, P2: c
// Completed
zip
Swift 표준 라이브러리 메서드와 비슷하게 쌍을 이루는 값의 튜플 형식의 값을 내보냅니다.
다른 publisher가 값을 내보낼 때까지 대기하는 방식입니다.
publisher2가 여러 번 값을 내보낸 뒤 종료가 되었다면 그 값을 무시됩니다.
Key points
- switchToLatest는 최신 publisher로 전환하고 이전 publisher의 구독을 취소합니다.
- merge는 여러 publisher의 값을 연결할 수 있습니다.
- combineLatest는 결합된 모든 publisher가 하나 이상의 값을 내보내면 모든 publisher가 값을 내보낼 때마다 결합된 최신 값을 내보냅니다.
- zip은 쌍으로 묶어 모든 publisher가 값을 내보내면 튜플 형식으로 값을 내보냅니다.