-
Chapter 4: FilteringOperatorsRaywenderlich/Combine: Asynchronous Programming 2020. 7. 17. 09:27
Version
Swift 5.3, iOS 14, Xcode 12
연산자(operators)는 기본적으로 Combine의 publishers를 조작(manipulate)하는 데 사용하는 어휘(vocabulary)이다. "단어(words)"를 많이 알수록, 데이터를 보다 잘 제어할 수 있다.
이전(previous) 장(chapter)에서는 값(values)을 사용(consume)하고 다른 값(values)으로 변환(transform)하는 방법을 배웠다. 이는 분명히, 일반적인 작업에서 가장 유용한 연산자(operator) 범주(categories) 중 하나이다.
그러나 publisher가 내보(emitted)낸 값(values)이나 이벤트(events)를 제한(limit)하고, 일부만 사용(consume)하려는 경우가 있을 수도 있다. 이 장(chapter)에서는 필터링 연산자(filtering operators)를 사용하여 이런 작업을 수행하는 방법을 설명한다.
다행히도 이러한 연산자(operators) 중 다수는 Swift 표준 라이브러리(standard library)에 동일한 이름으로 존재한다.
Getting started
이 장(chapter)의 프로젝트(project) 폴더(folder)에서 Starter.playground를 찾을 수 있다. 이 장(chapter)을 진행하면서 playground에 코드를 작성하고, 실행(run)한다. 그렇게 하면서 여러 연산자(operators)가 어떻게 publisher가 내보(emitted)낸 이벤트(events)를 조작(manipulate)하는지 이해하게 될 것이다.
Note: 이 장(chapter) 대부분의 연산자(operators)에
try
접두사(prefix)를 추가한 유사(parallels) 연산자가 있다(예 : filter vs. tryFilter). 이들 사이의 유일한 차이점은 후자(tryFilter)가 오류를 처리하는(throw) 클로저(closure)를 제공한다는 것이다. 클로저(closure) 내에서 발생(thrown)하는 모든 오류(error)는 해당 오류(error)와 함께 publisher를 종료(terminate)한다. 오류 처리가 없는(non-throwing) 연산자와 사실상 동일하므로 이 장(chapter)에서는 따로 다루지 않는다.Filtering basics
첫 번째 섹션(section)에서는 publisher의 값(values)을 사용(consuming)하고, 그 중 어떤 것을 전달할 지 조건부(conditionally)로 결정하는 필터링(filtering)의 기본에 대해 다룬다.
가장 쉬운 방법은 이름 그대로(aptly-named)
filter
연산자(operator)를 사용하는 것이다. filter는Bool
을 반환(returning)하는 클로저(closure)를 사용해, 제공된(provided) 조건(predicate)과 일치하는 값(values)만 전달한다:이 예제를 playground에 추가한다:
example(of: "filter") { let numbers = (1...10).publisher //1 ~ 10까지의 정수를 내보내고 완료하는 publisher //Sequence 타입에서 publisher 속성을 사용해 간단하게 publisher를 생성할 수 있다. numbers .filter { $0.isMultiple(of: 3) } //3의 배수만 필터링한다. .sink(receiveValue: { n in //구독 print("\(n) is a multiple of 3!") }) .store(in: &subscriptions) //저장 }
playground를 실행(run)하면, 콘솔(console)의 출력은 다음과 같다:
——— Example of: filter ———
3 is a multiple of 3!
6 is a multiple of 3!
9 is a multiple of 3!앱 수명(lifetime)동안 무시(ignore)하고 싶은 동일한(identical) 값(values)을 연달아(in a row) 내보내(emit)는 publishers가 있을 수 있다. 예를 들어, 사용자가 "a"를 연속(in a row)해서 5번 입력(types)한 다음 "b"를 입력(types)한다면, 과도하게(excessive) 입력된 "a"를 무시(disregard)해야 할 것이다.
Combine은 이와 같은 작업을 위해
removeDuplicates
라는 완벽한 연산자(operator)를 제공한다:이 연산자(operator)는 인수(arguments)가 필요 없다.
removeDuplicate
는String
을 포함하여,Equatable
을 준수(conforming)하는 모든 값(values)에 대해 자동으로 작동한다.removeDuplicates()
예제를 playground에 추가하고words
변수(variable)의 공백(space) 앞에?
를 포함한다:example(of: "removeDuplicates") { let words = "hey hey there! want to listen to mister mister ?" .components(separatedBy: " ") //words를 공백 단위로 나눈다. .publisher //publisher 생성. 단어들을 내보낸다. words .removeDuplicates() //단위별로 연속되는 중복 제거 //hey hey there hey 에 적용한다면 hey there hey가 된다. .sink(receiveValue: { print($0) }) //구독 .store(in: &subscriptions) //저장 }
playground를 실행(run)하고, 디버그(debug) 콘솔(console)을 살펴본다:
——— Example of: removeDuplicates ———
hey
there!
want
to
listen
to
mister
?보다시피, 두 번째 "hey"와 두 번째 "mister"를 생략(skipped)한다.
Note:
Equatable
을 준수(conform)하지 않는 값(values)을 사용해야 할 때는, 두 개의 값으로 클로저(closure)를 취하는removeDuplicates
의 또 다른 버전(overload)을 사용할 수 있다. 여기에서Bool
을 반환(return)하여 값이 동일한지 확인한다.Compacting and ignoring
Optional
값(values)을 내보(emitting)내는 publisher를 다뤄야 경우가 종종 있다. 또는 더 일반적으로,nil
을 반환(return)할 수 있는 값(values)을 처리해야 한다.Swift 표준 라이브러리(standard library)
Sequence
의CompactMap
과 같은 이름의 연산자(operator)가 Combine에도 있다.map, flatMap, compactMap 비교
https://jinshine.github.io/2018/12/14/Swift/22.%EA%B3%A0%EC%B0%A8%ED%95%A8%EC%88%98(2)%20-%20map,%20flatMap,%20compactMap/다음을 playground에 추가한다:
example(of: "compactMap") { let strings = ["a", "1.24", "3", "def", "45", "0.23"].publisher //문자열 배열을 내보내는 publisher를 생성한다. strings .compactMap { Float($0) } //각 String을 Floatd으로 변환한다. //문자열로 변환할 수 없다면 nil을 반환한다. .sink(receiveValue: { print($0) }) //성공적으로 Float으로 변환된 문자열만 출력한다. .store(in: &subscriptions) //저장 }
예제를 playground에서 실행(run)하면, 위의 도표(diagram)와 유사한 출력을 확인할 수 있다:
——— Example of: compactMap ———
1.24
3.0
45.0
0.23때로는 publisher가 내보(emitting)내는 실제 값(values)을 무시하고, 방출(emitting)이 완료(finished)되었다는 사실만 알고 싶을 때가 있다. 이러한 상황에는
ignoreOutput
연산자(operator)를 사용할 수 있다.위의 도표(diagram)에서 볼 수 있듯이 어떤 값이 방출(emitted)되는지 또는 얼마나 많은 값들이 방출(emitted)되는지는 중요하지 않다. 완료(completion) 이벤트(event)를 전달하기만 하면 된다.
playground에 다음 코드를 추가하여, 예제를 확인해 본다:
example(of: "ignoreOutput") { let numbers = (1...10_000).publisher //1 ~ 10,000까지 값을 내보내는 publisher를 생성한다. numbers .ignoreOutput() //모든 값을 생략하고 완료 이벤트만 방출한다. .sink(receiveCompletion: { print("Completed with: \($0)") }, receiveValue: { print($0) }) .store(in: &subscriptions) //저장 }
어떤 값(values)도 출력되지 않는다. playground를 실행하고 디버그(debug) 콘솔(console)을 확인해 본다:
——— Example of: ignoreOutput ———
Completed with: finishedFinding values
이 섹션(section)에서는 Swift 표준 라이브러리(standard library)에도 있는 두 연산자,
first(where:)
와last(where:)
에 대해 설명한다. 이름에서 알 수 있듯이, 각 제공된 조건(predicate)과 일치하는 첫 번째(first) 또는 마지막(last) 값(value)만 찾아서 내보(emit)낸다.first(where:)
부터 시작하여 몇 가지 예제를 확인한다.이 연산자는 lazy이기 때문에 흥미롭다. 즉, 제공한 조건(predicate)와 일치(match)하는 항목을 찾을(finds) 때까지 필요한 만큼의 값만 가져온다. 일치하는 항목을 찾으면, 구독(subscription)을 취소(cancels)하고 완료(completes)한다.
다음 코드를 playground에 추가하여 어떻게 작동하는지 확인한다:
example(of: "first(where:)") { let numbers = (1...9).publisher //1 ~ 9까지 값을 내보내는 publisher를 생성한다. numbers .first(where: { $0 % 2 == 0 }) //처음으로 방출된 짝수를 찾는다. .sink(receiveCompletion: { print("Completed with: \($0)") }, receiveValue: { print($0) }) .store(in: &subscriptions) //저장 }
playground에서 예제를 실행(run)하고 콘솔(console)의 출력(output)을 확인한다:
——— Example of: first(where:) ———
2
Completed with: finished생각했던 것처럼 작동합니다. 그러나 업 스트림(upstream) 구독(subscription)이
numbers
publisher를 의미하는지 생각해볼 필요가 있다. 짝수(even)를 찾아내고도 계속해서 값(values)을 내보(emitting)내는지 확인해보기 위해 아래 행(line)을 찾는다:numbers
그런 다음 해당 행(line) 바로 뒤에
print("numbers")
연산자(operator)를 추가한다. 다음과 같이 된다:numbers .print("numbers")
Note: 운영자(operator) 체인(chain)의 어디에서든
print
연산자(operator)를 사용하여, 해당 시점에서 어떤 이벤트(events)가 발생하는지 정확하게 확인할 수 있다.playground을 다시 실행(run)하고 살펴본다. 출력(output)은 다음과 같아야 한다:
——— Example of: first(where:) ———
numbers: receive subscription: (1...9)
numbers: request unlimited
numbers: receive value: (1)
numbers: receive value: (2)
numbers: receive cancel
2
Completed with: finished보다시피,
first(where:)
가 일치하는 값(matching value)을 찾으면, 구독(subscription)에 취소(cancellation)를 전송(sends)하여 업 스트림(upstream)에서의 값 방출(emitting)을 중지한다. 매우 편리하게 사용할 수 있다.이 연산자(operator)와는 반대로,
last(where:)
는 제공된 조건(predicate)과 일치(matching)하는 마지막 값(value)을 찾는 것이 목적(purpose)이다.first(where:)
와 달리, 이 연산자(operator)는 일치하는 값이 있는지 여부를 알기 위해 모든 값(all values)이 방출(emit)될 때까지 기다려야한다(greedy). 따라서, 업 스트림(upstream)은 특정 시점에 완료(completes)되는 publisher이여야 한다.playground에 다음 코드를 추가한다:
example(of: "last(where:)") { let numbers = (1...9).publisher //1 ~ 9까지 값을 내보내는 publisher를 생성한다. numbers .last(where: { $0 % 2 == 0 }) //마지막으로 방출된 짝수를 찾는다. .sink(receiveCompletion: { print("Completed with: \($0)") }, receiveValue: { print($0) }) .store(in: &subscriptions) //저장 }
playground을 실행(run)하면, 다음과 같은 결과를 확인할 수 있다:
——— Example of: last(where:) ———
8
Completed with: finished앞서, 이 연산자(operator)가 작동하려면 publisher가 완료(complete)되야한다고 언급했다.
연산자(operator)에서 publisher가 조건(criteria)과 일치하는 값을 내보낼(emit) 것인지 알 수 있는 방법이 없기 때문에, 연산자(operator)는 publisher의 전체 범위를 알아야 조건(predicate)과 일치하는 마지막 항목(last item)을 결정할 수 있다.
이를 확인해보기 위해, 예제를 다음과 같이 변경한다:
example(of: "last(where:)") { let numbers = PassthroughSubject<Int, Never>() //변경 numbers .last(where: { $0 % 2 == 0 }) .sink(receiveCompletion: { print("Completed with: \($0)") }, receiveValue: { print($0) }) .store(in: &subscriptions) //저장 numbers.send(1) numbers.send(2) numbers.send(3) numbers.send(4) numbers.send(5) }
이 예제에서는
PassthroughSubject
를 사용하고, 수동으로(manually) 이벤트(events)를 내보(send)낸다.다시 playground를 실행(run)하면, 아무 것도 출력되지 않는다:
——— Example of: last(where:) ———
publisher는 완료(completes)되지 않기 때문에 조건(criteria)과 일치하는 마지막 값(last value)을 결정할 방법이 없다.
이 문제를 해결(fix)하려면, 예제의 마지막 행(line)에 다음을 추가하여 subject의 완료(completion)를 내보내(send)야 한다:
numbers.send(completion: .finished)
playground를 다시 실행(run)하면, 모든 것이 예상대로 작동한다:
——— Example of: last(where:) ———
4
Completed with: finishedDropping values
값 제거(dropping values)는 publishers를 사용할 때 종종 사용하는 유용한 기능이다. 예를 들어 한 publisher의 값(values)을 두 번째 publisher가 게시(publishing)하기 전까지 무시하거나, 스트림(stream) 시작 시 특정 값(values)을 무시하려는 경우 사용할 수 있다.
이 범주(category)에는 세 개의 연산자(operators)가 있으며, 가장 간단한
dropFirst
부터 배운다.dropFirst
연산자(operator)는count
매개 변수(parameter, 기본값은1
)를 사용하며, publisher가 내보내(emitted)는 첫 번째count
값(values)을 무시(ignores)한다.count
값(values)이 방출(emitted)된 후에, 내보내진(emitted) 값들만 통과할 수 있다.playground에 코드를 추가하여, 이 연산자(operator)를 사용해 본다:
example(of: "dropFirst") { let numbers = (1...10).publisher //1 ~ 10까지 값을 내보내는 publisher를 생성한다. numbers .dropFirst(8) //첫 8개의 값을 생략한다. .sink(receiveValue: { print($0) }) //9, 10만 출력 .store(in: &subscriptions) //저장 }
playground를 실행(run)하면, 다음과 같은 결과가 출력된다:
——— Example of: dropFirst ———
9
10유용한 연산자(operators)들은 종종 이와같이 매우 간단하게 사용한다.
값 제거(dropping values) 계열(family)의 다음 연산자(operator)는
drop(while:)
이다. 이는 조건(predicate) 클로저(closure)를 사용해, 조건(predicate)이 처음으로 만족할 때까지 publisher가 내보낸(emitted) 값(values)을 무시하는 매우 유용한 연산자이다. 조건(predicate)이 만족되는 즉시, 값(values)이 연산자(operator)를 통과(flow through)하기 시작한다:다음 예제를 playground에 추가하여 확인한다:
example(of: "drop(while:)") { let numbers = (1...10).publisher //1 ~ 10까지 값을 내보내는 publisher를 생성한다. numbers .drop(while: { $0 % 5 != 0 }) .sink(receiveValue: { print($0) }) //5로 나누어 떨어지는 첫 번째 값을 기다린다. //조건이 충족되는 즉시 값들은 통과하기 시작하며, 더 이상 값이 제거되지 않는다. .store(in: &subscriptions) //저장 }
playground를 실행(run)하고, 디버그(debug) 콘솔(console)을 확인한다:
——— Example of: drop(while:) ———
5
6
7
8
9
10보다시피 처음 네개의 값(values)을 삭제(dropped)했다.
5
는 조건을 만족(true
)하므로,5
와 이후의(future) 모든 값(values)을 내보(emits)낸다.이 연산자(operator)는
filter
와 비슷해 보일 수도 있다. 두 연산자 모두 클로저(closure)의 결과에 따라 어떤 값이 방출(emitted)될 지 제어한다.첫 번째 차이점(difference)은 클로저(closure)에서
true
를 반환(return)하면,filter
에서는 값(values)이 통과하지만drop(while:)
에서는 해당 값을 건너뛴다(skips).두 번째로 더 중요한 차이점은,
filter
는 업 스트림(upstream) publisher가 게시하는(published) 모든 값(all values)에 대해 조건(condition)을 확인한다는 것이다.filter
에서는 조건이true
가 되더라도, 이후의 값들을 계속해서 확인한다.이와 달리
drop(while:)
은 조건(condition)이 충족되면, 이후부터는 다시 조건을 확인하지 않는다. 이를 확인하려면 다음 행(line)을.drop(while: { $0 % 5 != 0 })
아래와 같이 변경한다:
.drop(while: { print("x") return $0 % 5 != 0 })
클로저(closure)가 호출(invoked)될 때마다, 디버그(debug) 콘솔(console)에
x
를 출력하는print
구문(statement)을 추가했다. playground를 실행(run)하면 다음과 같은 결과를 확인할 수 있다:——— Example of: drop(while:) ———
x
x
x
x
x
5
6
7
8
9
10보다시피,
x
는 정확히5
번 출력된다. 조건(condition)이 충족되자마자(5가 방출될 때), 클로저(closure)는 다시 확인하지(evaluated) 않는다.지금까지 2가지 제거 연산자(dropping operators)을 알아봤다. 아직 하나가 남아있다.
필터링(filtering) 범주(category)의 최종적(final)이고 가장 정교한(elaborate) 연산자(operator)인
drop(untilOutputFrom:
)이다.사용자가 버튼(button)을 눌러도,
isReady
publisher가 결과(result)를 내보낼(emits) 때까지 모든 탭(tapping)을 무시(ignore)하는 경우를 생각해 본다. 이 연산자(operator)는 이러한 조건에 적합(perfect)하다.두 번째 publisher가 값(values)을 내보내기(emitting) 시작할 때까지, publisher가 내보낸(emitted) 값(values)을 건너 뛰어(skips) 다음과 같은 관계(relationship)를 만든다:
맨 위 줄(top line)은
isReady
스트림(stream)을 나타내고, 두 번째 줄(line)은drop(untilOutputFrom:)
을 통과하는 사용자의 탭(taps)을 나타내며isReady
를 인수(argument)로 사용한다.playground에 이 도표(diagram)를 재현하는 다음 코드를 추가한다:
example(of: "drop(untilOutputFrom:)") { let isReady = PassthroughSubject<Void, Never>() //isReady 상태 let taps = PassthroughSubject<Int, Never>() //사용자에 의한 탭 //수동으로 값을 내보낼 수 있는 두 개의 PassthroughSubject를 생성한다. taps .drop(untilOutputFrom: isReady) //isReady에서 값을 하나 이상 내보낼 때까지 탭을 무시한다. .sink(receiveValue: { print($0) }) .store(in: &subscriptions) //저장 (1...5).forEach { n in //5개의 탭을 내보낸다. taps.send(n) if n == 3 { //3번째 탭일때, isReady에서 값을 내보낸다. isReady.send() } } }
playground를 실행(run)한 다음, 디버그(debug) 콘솔(console)을 살펴보면 다음과 같다:
——— Example of: drop(untilOutputFrom:) ———
4
5이 출력(output)은 위의 도표(diagram)와 동일하다:
- 사용자의 탭(taps)이 5번 있다. 처음 세 번은 무시(ignored)한다.
- 3번째 탭(tap) 이후,
isReady
는 값(value)을 내보(emits)낸다. - 이후, 사용자의 모든 탭(taps)은 통과(passed through)된다.
원치 않는 값(values)을 제거하는 데 꽤 익숙해졌을 것이다. 이제 마지막 필터링(filtering) 연산자(operators) 범주(group)인 값의 제한(Limiting values)에 대해 알아본다.
Limiting values
이전 섹션(section)에서는 특정 조건(condition)이 충족될 때까지 값(values)을 삭제(drop)하거나 건너뛰는(skip) 방법을 배웠다. 그 조건(condition)은 어떤 정적 값(static value), 조건 클로저(predicate closure), 다른 publisher에 대한 종속성(dependency) 등으로 일치 여부를 판별할 수 있다.
이 섹션(section)에서는 반대로, 조건(condition)이 충족될 때까지 값(values)을 수신(receiving)한 후 publisher가 완료(complete)되도록 하는 방법을 배운다. 예를 들어, 알 수 없는 양(amount)의 값(values)을 내보내(emit)지만, 단일 방출(single emission)만 하며 나머지(rest)의 값들은 신경 쓰지 않는 요청(request)을 고려(consider)해 볼 수 있다.
Combine은 이 일련의 문제를
prefix
계열(family) 연산자(operators)를 사용하여 해결(solves)한다. 이름이 완전히 직관적(intuitive)이지는 않지만, 이러한 연산자(operators)가 제공하는 기능은 다양한 실제 상황에서 유용하다.연산자(operators)의
prefix
계열(family)은drop
계열(family)과 유사하며,prefix(_:)
,prefix(while:)
,prefix(untilOutputFrom:)
가 있다. 그러나 해당 조건(condition)이 충족될 때까지 값(values)을 삭제(dropping)하는 대신,prefix
연산자(operators)는 그 조건(condition)이 충족될 때까지 값(values)을 가져(take)온다.prefix(_:)
를 시작으로, 이 장(chapter)의 마지막 연산자(operators) 집합(set)을 살펴본다.dropFirst
와는 반대로prefix(_:)
는 제공된 양(amount)까지만 값(values)을 가져 와서 완료(complete)한다:playground에 다음 코드를 추가한다:
example(of: "prefix") { let numbers = (1...10).publisher //1 ~ 10까지 값을 내보내는 publisher를 생성한다. numbers .prefix(2) //처음 두 값만 방출. //2개의 값을 내보내면 publisher는 완료된다. .sink(receiveCompletion: { print("Completed with: \($0)") }, receiveValue: { print($0) }) .store(in: &subscriptions) //저장 }
playground를 실행(run)하면, 다음과 같은 출력(output)을 확인할 수 있다.
——— Example of: prefix ———
1
2
Completed with: finishedfirst(where:)
와 마찬가지로, 이 연산자(operator)는 lazy이다. 필요한 만큼만 값(values)을 가져오고 종료(terminates)한다. 또한 완료(completes)되기 때문에,numbers
가1
과2
를 넘어서는 추가 값(additional values)을 생성(producing)하는 것을 막는다(prevents).다음은
prefix(while:)
로, 조건(predicate) 클로저(closure)를 가져와 해당 결과가true
인 동안(as long as) 업 스트림(upstream) publisher의 값(values)을 허용한다. 결과가false
이되면, publisher는 완료(complete)될 것이다:playground에 다음 예제를 추가한다:
example(of: "prefix(while:)") { let numbers = (1...10).publisher //1 ~ 10까지 값을 내보내는 publisher를 생성한다. numbers .prefix(while: { $0 < 3}) //3보다 작을 때 통과한다. //3보다 크거나 같은 값을 내보내면, publisher는 완료된다. .sink(receiveCompletion: { print("Completed with: \($0)") }, receiveValue: { print($0) }) .store(in: &subscriptions) //저장 }
이 예제는 접두사(prefixing) 조건(condition)을 평가하기(evaluate) 위해, 클로저(closure)를 사용한다는 점 외에는 이전 예제와 거의 동일하다.
playground를 실행(run)한 다음, 디버그(debug) 콘솔(console)을 확인한다. 이전 연산자(operator)의 출력(output)과 동일해야 한다.:
——— Example of: prefix(while:) ———
1
2
Completed with: finished다음은 가장 복잡한
prefix(untilOutputFrom:)
연산자(operator)이다. 다시 한 번 언급하지만, 두 번째 publisher가 내보낼(emits) 때까지 값(values)을 건너 뛰는drop(untilOutputFrom:)
과 달리,prefix(untilOutputFrom:)
은 두 번째 publisher가 내보낼(emits) 때까지 값을 사용한다.사용자가 두 번만 누를 수있는 버튼(button)이 있다고 가정할 때, 두 번 누르는 즉시 버튼(button)의 추가 탭(tap) 이벤트(events)를 생략해야 한다:
마지막 예제를 playground에 추가한다:
example(of: "prefix(untilOutputFrom:)") { let isReady = PassthroughSubject<Void, Never>() //isReady 상태 let taps = PassthroughSubject<Int, Never>() //사용자에 의한 탭 //수동으로 값을 내보낼 수 있는 두 개의 PassthroughSubject를 생성한다. taps .prefix(untilOutputFrom: isReady) //isReady에서 값을 하나 이상 내보낼 때까지 통과시킨다. .sink(receiveCompletion: { print("Completed with: \($0)") }, receiveValue: { print($0) }) .store(in: &subscriptions) //저장 (1...5).forEach { n in //5개의 탭을 내보낸다. taps.send(n) if n == 2 { //2번째 탭일때, isReady에서 값을 내보낸다. isReady.send() } } }
drop(untilOutputFrom:)
예제를 상기해본다면 더 이해하기 쉬울 것이다.playground를 실행(run)한다. 콘손(console)은 다음은 같이 출력되어야 한다:
——— Example of: prefix(untilOutputFrom:) ———
1
2
Completed with: finishedChallenge
지금까지 필터링(filtering)에 대해 꽤 많은 것을 배웠다. 과제(challenge)를 진행해보면서 복습해 본다.
Challenge: Filter all the things
1
에서100
까지의 숫자 컬렉션(collection)을 게시(publishes)하는 예제를 만들고, 필터링 연산자(filtering operators)를 사용하여 다음 작업을 수행한다:- 업 스트림(upstream) publisher가 내보낸(emitted) 처음 50개 값(values)은 건너뛴다(skip).
- 첫 50개의 값(values) 이후에 다음 20개 값(values)을 가져온다.
- 짝수만 가져온다.
예제의 출력은 한 줄에 하나씩 다음과 같은 숫자가 되어야 한다:
52 54 56 58 60 62 64 66 68 70
Note 이 챌린지(challenge)에서는 여러(multiple0 연산자(operators)를 함께 연결(chain)하여, 원하는 값(desired values)을 산출(produce)해야 한다.
projects/challenge/ Final.playground에서 이 챌린지(challenge)에 대한 완전한 해결책(solution)을 확인할 수 있다.
solution
let numbers = (1...100).publisher numbers .dropFirst(50) //1번 .prefix(20) //2번 .filter { $0 % 2 == 0} //3번 .sink(receiveValue: { print($0) }) .store(in: &subscriptions)
Key points
이 장(chapter)에서 배운 내용은 다음과 같다:
- 필터링 연산자(filtering operators)를 사용하면 업 스트림(upstream) publisher가 다운 스트림(downstream), 다른 연산자(another operator), 소비자(consumer) 등 에게 내보낸(emitted) 값(values)을 제어(control)할 수 있다.
- 값(values) 자체에 신경 쓸 필요없고, 완료(completion) 이벤트(event)만 필요한 경우
ignoreOutput
을 사용할 수 있다. - 값 찾기(Finding values)는 다른 종류(sort)의 필터링(filtering)으로,
first(where:)
와last(where:)
를 사용하여 각각 제공된(provided) 조건(predicate)과 일치하는 첫 번째(first) 또는 마지막(last) 값을 찾을 수 있다. - first 유형(First-style)의 연산자(operators)는 lazy이다. 필요한 만큼의 값(values)만 가져온 다음 완료(complete)한다. last 유형(Last-style)의 연산자(operators)는 조건(condition)을 충족(fulfill)시키는 마지막 값(values)을 결정하기 전에 값의 전체 범위(scope)를 알아야 한다(greedy).
drop
계열(family) 연산자(operators)를 사용하여 다운 스트림(downstream) 값(values)을 내보내(sending)기 전에 업 스트림(upstream) publisher가 내보낸(emitted) 값(values)을 얼만큼 무시(ignored)할 지 제어(control)할 수 있다.- 마찬가지로
prefix
계열(family) 연산자(operators)를 사용하여, 완료(completing) 전에 업 스트림(upstream) publisher가 내보낼(emit) 수 있는 값(values)의 수를 제어(control)할 수 있다.
'Raywenderlich > Combine: Asynchronous Programming' 카테고리의 다른 글
Chapter 6: Time Manipulation Operators (0) 2020.07.27 Chapter 5: Combining Operators (0) 2020.07.20 Chapter 3: TransformingOperators (0) 2020.07.15 Chapter 2: Publishers & Subscribers (0) 2020.07.05 Chapter 1: Hello, Combine! (0) 2020.07.02