[Swift] Reactive Programming Combine - 4: Filtering Operators
Filtering Operatros
filter
어떤 값을 전달할 지 조건부로 결정하는 filter입니다.
var subscriptions = Set<AnyCancellable>()
let numbers = (1...6).publisher
numbers
.filter { $0.isMultiple(of: 2) }
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
// *--- RESULT --*
// 2
// 4
// 6
removeDuplicates
동일한 값을 연속으로 내보내는 publisher를 무시합니다.
let words = "hey hey there! want to listen to mister mister ? ? ?"
.components(separatedBy: " ")
.publisher
words
.removeDuplicates()
.sink(receiveValue: { print($0, terminator: " ") })
.store(in: &subscriptions)
// *--- RESULT ---*
// hey there! want to listen to mister ?
Equtable에 부합하지 않는 값이라면 클로저에서 값이 같은지를 Bool로 반환하면 됩니다.
let tuples = [(1, 3), (1, 3), (2, 2), (2, 4), (2, 2), (1, 3), (1, 3)]
.publisher
tuples
.removeDuplicates { $0.0 == $1.0 && $0.1 == $1.1 }
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
// *--- RESULT ---*
// (1, 3)
// (2, 2)
// (2, 4)
// (2, 2)
// (1, 3)
compactMap
optional value를 반환하는 publisher인 경우, 모든 nil을 처리할 때 사용합니다.
let strings = ["a", "1.24", "3",
"def", "45", "0.23"].publisher
strings
.compactMap { Float($0) }
.sink(receiveValue: {
print($0)
})
.store(in: &subscriptions)
// *--- RESULT ---*
// 1.24
// 3.0
// 45.0
// 0.23
ignoreOutput
publisher의 실제 값을 무시하고 값만 output 했다는 사실만 알고 싶은 경우에 사용됩니다.
let numbers = (1...10_000).publisher
numbers
.ignoreOutput()
.sink(receiveCompletion: { print("Completed with: \($0)") },
receiveValue: { print($0) })
.store(in: &subscriptions)
// *--- RESULT ---*
// Completed with: finished
first
조건에 맞는 첫 번째 값만 내보냅니다.
조건과 일치하는 값을 찾은 뒤, 구독을 통해 취소를 전송하여 upstream에서 값 전송을 중지합니다.
let numbers = (1...9).publisher
.print("numbers")
numbers
.first(where: { $0 % 2 == 0 })
.sink(receiveCompletion: { print("Completed with: \($0)") },
receiveValue: { print($0) })
.store(in: &subscriptions)
// *--- RESULT ---*
// numbers: receive subscription: (1...9)
// numbers: request unlimited
// numbers: receive value: (1)
// numbers: receive value: (2)
// numbers: receive cancel
// 2
// Completed with: finished
last
조건에 일치하는 마지막 값만 내보냅니다.
first와 달리 일치하는 값을 찾았는지 확인하기 위해 모든 값이 나올 때까지 기다려야 합니다.
따라서 upstream은 완료되는 publisher여야 합니다.
dropFirst
특정 개수의 값을 무시할 때 사용합니다.
drop(while:)
조건을 처음 충족할 때까지 내보내는 모든 값을 무시합니다.
조건문에서 false를 무시하다가 true가 되는 시점 이후 모든 값을 내보냅니다.
조건이 충족된 후에는 다시 실행되지 않습니다.
let numbers = (1...10).publisher
numbers
.drop(while: {
print("x")
return $0 % 4 != 0
})
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
// *--- RESULT ---*
// x
// x
// x
// x
// 4
// 5
// 6
// 7
// 8
// 9
// 10
drop(untilOutputFrom:)
특정 publisher가 어떤 값을 내보낼 때까지 모든 값을 무시합니다.
isReady에 값이 오기 전까지 timerPublisher의 값들은 무시됩니다.
prev가 4가 되면 isReady에 Void 값을 내보내고 5부터 출력됩니다.
let isReady = PassthroughSubject<Void, Never>()
let timerPublisher = Timer.publish(every: 1.0, tolerance: 0.1, on: .main, in: .default)
.autoconnect()
.scan(0) { prev, _ in
if prev % 5 == 4 {
isReady.send()
}
return prev + 1
}
timerPublisher
.drop(untilOutputFrom: isReady)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
prefix
dropFirst와 반대인 prefix는 제공된 양까지만 값을 가져온 다음 완료합니다.
first(where:)과 마찬가지로 lazy하기 때문에, 필요한 만큼의 값만 취하고 종료합니다.
let numbers = (1...10).publisher
numbers
.prefix(2)
.sink(receiveCompletion: { print("Completed with: \($0)") },
receiveValue: { print($0) })
.store(in: &subscriptions)
// *--- RESULT ---*
// 1
// 2
// Completed with: finished
prefix(while:)
클로저를 사용하여 해당 결과가 true면 upstream 값을 통과시킵니다. 결과가 false라면 publisher가 완료됩니다.
let numbers = (1...10).publisher
numbers
.prefix(while: { $0 < 3 })
.sink(receiveCompletion: { print("Completed with: \($0)") },
receiveValue: { print($0) })
.store(in: &subscriptions)
// *--- RESULT ---*
// 1
// 2
// Completed with: finished
prefix(untilOutputFrom:)
두번째 publisher가 값을 내보낼 때까지 값을 가져옵니다.
두번째 publisher에서 값을 내보내면 종료합니다.
let isReady = PassthroughSubject<Void, Never>()
let timerPublisher = Timer.publish(every: 1.0, tolerance: 0.1, on: .main, in: .default)
.autoconnect()
.scan(0) { prev, _ in
if prev % 5 == 4 {
isReady.send()
}
return prev + 1
}
timerPublisher
.prefix(untilOutputFrom: isReady)
.sink(receiveCompletion: {print("Completion with: \($0)")},
receiveValue: { print($0) })
.store(in: &subscriptions)
// *--- RESULT ---*
// 1
// 2
// 3
// 4
// Completion with: finished
Key points
- Filtering operators를 사용하면 upstream publisher가 어떤 값을 보낼지 제어할 수 있습니다.
- 값 자체를 신경쓰지 않고 완료 이벤트만 원할 때에는 ignoreOutput을 사용합니다.
- provided predicate (클로저에서 주어진 조건문)과 일치하는 첫번째 또는 마지막 값은 first(where:) 및 last(where:)로 찾을 수 있습니다.
- First-style의 operators는 lazy하기 때문에 필요한 만큼의 값만 취한 다음 완료합니다.
- Last-style의 operators는 greedy하기 때문에 마지막 값인지 알기 위해 전체 범위를 알아야 합니다.
- drop 계열의 operators는 downstream으로 보내기 전에 무시할 값의 수를 제어할 수 있습니다.
- prefix 계열의 operators는 upstream publisher가 완료하기 전에 방출할 수 있는 값의 수를 제어할 수 있습니다.