-
Chapter 7: Sequence OperatorsRaywenderlich/Combine: Asynchronous Programming 2020. 8. 9. 04:27
Version
Swift 5.3, iOS 14, Xcode 12
Combine이 제공해야하는 대부분의 연산자(operators)에 대해 배웠지만, 자세히 살펴 보아야할 한 가지 범주(category)가 더 있다. 바로 시퀀스 연산자(Sequence Operators)이다.
시퀀스(sequence) 연산자(operators)는 publishers가 시퀀스(sequences) 자체일 뿐이라는 것을 깨달을(realize) 때 가장 이해하기 쉽다. 시퀀스(sequence) 연산자(operators)는 배열(array)이나 집합(set)과 같은 publisher의 값 컬렉션(collection)과 함께 작동한다. 배열(array)이나 집합(set)도 유한(finite) 시퀀스(sequences)일 뿐이다.
다른 범주(categories)의 연산자(operators)가 그러하듯이, 시퀀스(sequence) 연산자(operators)는 대부분의 시퀀스(sequence)를 개별(individual) 값(values)이 아닌 전체(whole)로 처리(deal)한다.
이 범주(category)에 속하는 대다수 연산자(operators)는 Swift 표준(standard) 라이브러리(library)의 해당 연산자(counterparts)와 거의 동일한 이름과 특성(behaviors)을 가지고 있다.
Getting started
projects/Starter.playground에서 이 장(chapter)의 시작(starter)playground를 찾을 수 있다. playground에 코드를 추가(add)하고 실행(run)하여, 다양한 시퀀스(sequence) 연산자(operators)가 publisher를 어떻게 조작(manipulate)하는지 확인한다.
print
연산자(operator)를 사용하여 모든 publishing 이벤트(events)를 기록한다.Finding values
첫 섹션(section)은 다른 기준(criteria)에 따라 publisher가 내보낸(emits) 특정(specific) 값(values)을 찾는 연산자(operators)로 구성된다. 이는 Swift 표준(standard) 라이브러리(library)의 collection 메서드(methods)와 유사하다.
min
min
연산자(operator)를 사용하여 publisher가 내보낸(emitted) 최소값(the minimum value)을 찾을 수 있다. publisher가.finished
완료(completion) 이벤트(event)를 보낼 때까지 기다려야 한다(greedy). publisher가 완료(completes)되면 연산자(operator)는 최소값(the minimum value)만 내보낸다(emitted):다음 예제를 playground에 추가하여,
min
의 사용 방법을 확인해 본다:example(of: "min") { let publisher = [1, -50, 246, 0].publisher //4개의 다른 숫자를 내보내는 publisher를 생성한다. publisher .print("publisher") .min() //min 연산자를 사용하여 publisher가 내보낸 값 중 최소 값을 찾는다. .sink(receiveValue: { print("Lowest value is \($0)") }) //출력 .store(in: &subscriptions) //저장 }
playground를 실행(run)하면, 콘솔(console)에서 다음과 같은 출력(output)을 확인할 수 있다:
——— Example of: min ———
publisher: receive subscription: ([1, -50, 246, 0]) publisher: request unlimited
publisher: receive value: (1)
publisher: receive value: (-50)
publisher: receive value: (246)
publisher: receive value: (0)
publisher: receive finished
Lowest value is -50보다시피 publisher가 모든 값(values)을 내보내고(emits) 완료(finishes)한 다음,
min
이 최소값(minimum)을 찾아 다운 스트림(downstream)으로 전송하여sink
에서 출력한다.Combine이 최소값(minimum)을 찾아낼 수 있는 이유는 숫자 값(numeric value)이
Comparable
프로토콜(protocol)을 준수하기 때문이다.Comparable
을 준수하는 유형(types)을 내보내(emit)는 publishers에서는 인수(arguments)없이min()
을 직접 사용할 수 있다.값(values)이
Comparable
을 준수(conform)하지 않을 때에는,min(by:)
연산자(operator)를 사용하여 자체 비교(comparator) 클로저(closure)를 제공할 수 있다.publisher가 내보낸(emits) 여러
Data
유형에서 가장 작은 값을 찾는 다음 예제를 고려해본다.playground에 다음 코드를 추가한다:
example(of: "min non-Comparable") { let publisher = ["12345", "ab", "hello world"] .compactMap { $0.data(using: .utf8) } // [Data] .publisher // Publisher<Data, Never> //다양한 문자열에서 생성된 세 개의 Data 객체를 내보내는 publisher를 생성한다. publisher .print("publisher") .min(by: { $0.count < $1.count }) //Data 유형은 Comparable을 구현하지 않았기 때문에, min(by:) 연산자를 사용한다. //여기서는 바이트 수가 가장 적은 Data 객체를 찾는다. .sink(receiveValue: { data in let string = String(data: data, encoding: .utf8)! //Data를 String으로 변환한다. print("Smallest data is \(string), \(data.count) bytes") }) .store(in: &subscriptions) //저장 }
playground를 실행(run)하면, 콘솔(console)에서 다음을 확인할 수 있다:
——— Example of: min non-Comparable ———
publisher: receive subscription: ([5 bytes, 2 bytes, 11 bytes]) publisher: request unlimited
publisher: receive value: (5 bytes)
publisher: receive value: (2 bytes)
publisher: receive value: (11 bytes)
publisher: receive finished
Smallest data is ab, 2 bytes이전(previous) 예제와 마찬가지로 publisher는 모든
Data
객체(objects)를 내보내고(emits) 완료(finishes)한 다음,min(by:)
으로 가장 작은 바이트(byte) 크기(size)의 데이터를 찾아 내보내고(emits)sink
가 이를 출력한다.max
짐작할 수 있듯이,
max
는 publisher가 내보낸(emitted) 최대 값(the maximum value)을 찾아낸다는 것만 제외하면min
과 똑같이 작동한다.playground에 다음 코드를 추가하여 해당 예제를 확인해 본다:
example(of: "max") { let publisher = ["A", "F", "Z", "E"].publisher //4개의 다른 문자를 내보내는 publisher를 생성한다. publisher .print("publisher") .max() //max 연산자를 사용하여 최대 값을 찾는다. .sink(receiveValue: { print("Highest value is \($0)") }) //출력 .store(in: &subscriptions) //저장 }
playground를 실행(run)한다. 다음과 같은 결과(output)를 확인할 수 있다:
——— Example of: max ———
publisher: receive subscription: (["A", "F", "Z", "E"])
publisher: request unlimited
publisher: receive value: (A)
publisher: receive value: (F)
publisher: receive value: (Z)
publisher: receive value: (E)
publisher: receive finished
Highest value is Zmin
과 같이max
는 최대 값(the maximum value)을 결정(determines)하기 전에 업 스트림(upstream) publisher가 모든 값을 내보낼(emitting) 때까지 기다려야(wait) 한다(greedy). 이 경우, 해당 값(value)은Z
이다.Note:
min
과 똑같이max
에는Comparable
이 아닌 값(values)에서 최대 값(the maximum value)을 결정(determine)하기 위한max(by:)
연산자(operator)가 있다.first
min
과max
연산자(operators)는 알려지지 않은(unknown) 인덱스(index)에서 published 값(value)을 찾는(finding) 것을 처리(deal with)했지만, 이 섹션(section)의 다른 연산자(operators)들은 특정(specific) 위치에서 방출(emitted)된 값(values)을 찾는(finding) 작업을 처리한다.first
연산자(operator)는 처음(first) 방출된(emitted) 값을 통과(through)시킨 다음 완료(completes)한다는 점을 제외하면, Swift 컬렉션(collections)에서의first
속성(property)과 유사하다. lazy이기 때문에 업 스트림(upstream) publisher가 완료(finish)될 때까지 기다리지 않고, 내보낸(emitted) 첫 번째(first) 값(value)을 수신(receives)하면 구독(subscription)을 취소(cancel)한다.위의 예제를 playground에 추가한다:
example(of: "first") { let publisher = ["A", "B", "C"].publisher //3개의 다른 문자를 내보내는 publisher를 생성한다. publisher .print("publisher") .first() //첫 번째로 내보낸 값만 통과시킨다. .sink(receiveValue: { print("First value is \($0)") }) //출력 .store(in: &subscriptions) //저장 }
playground를 실행(run)하고, 콘솔(console)을 확인한다:
——— Example of: first ———
publisher: receive subscription: (["A", "B", "C"])
publisher: request unlimited
publisher: receive value: (A)
publisher: receive cancel
First value is Afirst()
가 첫 번째 값(value)을 통과(through)시키자마자, 업 스트림(upstream) publisher에 대한 구독(subscription)을 즉시(immediately) 취소(cancels)한다.보다 세분화(granular)된 제어(control)를 원한다면
first(where:)
를 사용할 수도 있다. Swift 표준(standard) 라이브러리(library)에 있는 것과 마찬가지로 제공된(provided) 조건(predicate)과 일치(matches)하는 첫 번째(first) 값(value)을 내보낸다(emit).다음 예제를 playground에 추가한다:
example(of: "first(where:)") { let publisher = ["J", "O", "H", "N"].publisher //3개의 문자를 내보내는 publisher를 생성한다. publisher .print("publisher") .first(where: {"Hello World".contains($0) }) //Hello World에 포함된 첫 번째 문자를 찾는다. .sink(receiveValue: { print("First match is \($0)") }) //출력 .store(in: &subscriptions) //저장 }
playground를 실행(run)하면 다음과 같은 출력(output)을 확인할 수 있다:
——— Example of: first(where:) ———
publisher: receive subscription: (["J", "O", "H", "N"])
publisher: request unlimited
publisher: receive value: (J)
publisher: receive value: (O)
publisher: receive value: (H)
publisher: receive cancel
First match is H위의 예제에서 연산자(operator)는 첫 번째 일치점(match)을 찾을 때까지,
Hello World
에 내보낸(emitted) 문자가 포함(contains)되어 있는지 확인한다. 여기서는H
가 되고, 이를 찾으면 구독(subscription)을 취소(cancels)하고sink
에서 출력(print out)할 수 있도록 문자를 내보낸다(emits).last
max
가min
의 반대(opposite)인 것처럼,last
도first
의 반대(opposite)이다.last
는 publisher가 내보낸(emits) 마지막 값(the last value)을 방출(emits)한다는 점을 제외(except)하면first
와 똑같이 작동한다. 이는 업 스트림(upstream) publisher가 완료(finish)될 때까지 기다려야(wait) 한다는 것을 의미한다(greedy):이 예제를 playground에 추가한다:
example(of: "last") { let publisher = ["A", "B", "C"].publisher //3개의 문자를 내보내는 publisher를 생성한다. publisher .print("publisher") .last() //마지막 값만 내보낸다. .sink(receiveValue: { print("Last value is \($0)") }) //출력 .store(in: &subscriptions) //저장 }
playground를 실행(run)하면 다음과 같은 결과(output)를 확인할 수 있다:
——— Example of: last ———
publisher: receive subscription: (["A", "B", "C"])
publisher: request unlimited
publisher: receive value: (A)
publisher: receive value: (B)
publisher: receive value: (C)
publisher: receive finished
Last value is Clast
는 업 스트림(upstream) publisher가.finished
완료(completion) 이벤트(event)를 보낼(send) 때까지 기다린다. 마지막(last)으로 내보낸(emitted) 값(value)을 다운 스트림(downstream)으로 보내sink
에서 출력한다.Note: first와 마찬가지로, last에는 지정된 조건(predicate)과 일치하는 publisher가 내보낸(emitted) 마지막 값(the last value)을 방출(emitted)하는 last(where:) 연산자(operator)가 있다.
output(at:)
이 섹션(section)의 마지막 두 연산자(operators)는 Swift 표준(standard) 라이브러리(library)에 대응하는(counterparts) 연산자(operators)가 없다.
output
연산자(operators)는 지정된(specified) 인덱스(indices)에서 업 스트림(upstream) publisher가 값을 내보낸(emitted) 경우에만 값(values)을 통과시킨다.먼저, 지정된(specified) 인덱스(index)에서 방출된(emitted) 값(value)만 내보내(emits)는
output(at:)
이 있다:playground에 다음 코드를 추가하여, 이 예제를 확인해 본다:
example(of: "output(at:)") { let publisher = ["A", "B", "C"].publisher //3개의 문자를 내보내는 publisher를 생성한다. publisher .print("publisher") .output(at: 1) //인덱스 1에서 방출된 값. 즉, 두 번째 값만 통과시킨다. .sink(receiveValue: { print("Value at index 1 is \($0)") }) //출력 .store(in: &subscriptions) //저장 }
playground에서 예제를 실행(run)하고 콘솔(console)을 확인(peek at)한다:
——— Example of: output(at:) ———
publisher: receive subscription: (["A", "B", "C"])
publisher: request unlimited
publisher: receive value: (A)
publisher: request max: (1) (synchronous)
publisher: receive value: (B)
Value at index 1 is B
publisher: receive cancel여기서 출력은 인덱스
1
의 값(value)이B
임을 나타낸다. 그러나 추가적으로 흥미로운 사실을 알 수 있습니다. 연산자(operator)는 특정(specific) 인덱스(index)에서만 항목(item)을 찾는다는 것을 알고 있으므로, 내보낸(emitted) 값(value)마다 한 개의 값(value)을 더 요구(demands)한다. 이는 특정(specific) 연산자(operator)의 구현(implementation) 세부 사항(detail)이지만, Apple이 자체 내장된(built-in) Combine 연산자(operators)를 설계(designs)하는 방법에 대한 흥미로운 통찰력(insight)을 제공한다.output(in:)
output
연산자(operator)의 오버로딩(overload)으로 이 섹션(section)을 마무리(wrap up)한다.output(at:)
은 지정된(specified) 인덱스(index)에서 방출(emitted)된 단일 값(single value)을 내보내지만,output(in:)
은 지정된 인덱스(indices) 범위(range) 내에 있는 값들을 내보낸다:다음 예제를 playground에 추가하여, 이를 확인해 본다:
example(of: "output(in:)") { let publisher = ["A", "B", "C", "D", "E"].publisher //5개의 서로 다른 문자를 내보내는 publisher를 생성한다. publisher .output(in: 1...3) //인덱스 1 ~ 3 까지에서 방출된 값만 통과시킨다. .sink(receiveCompletion: { print($0) }, receiveValue: { print("Value in range: \($0)") }) //출력 .store(in: &subscriptions) //저장 }
이 예제의 출력을 확인하기 위해, playground를 실행(run)한다:
——— Example of: output(in:) ———
Value in range: B
Value in range: C
Value in range: D
finished연산자(operator)는 인덱스 모음(collection)이 아닌, 인덱스 범위(range of indices) 내의 개별 값(individual values)을 내보낸다(emits).
연산자(operator)는 각각(respectively) 인덱스(indices)
1
,2
,3
에 있는B
,C
,D
값(values)을 출력한다. 그런 다음 범위(range) 내의 모든 항목(items)이 방출(emitted)되었으므로, 작업을 완료(complete)하는 데 필요한 모든 항목을 받는(receives) 즉시 구독(subscription)을 취소(cancels)한다.Querying the publisher
다음 연산자(operators)도 publisher가 내보낸(emitted) 전체 값 집합(the entire set of values)을 처리(deal with)하지만, 내보낸(emits) 특정(specific) 값(value)을 산출(produce)하지는 않는다. 대신 이런 연산자들(operators)은 publisher 전체(whole)에 대한 어떤 질의(query)를 나타내는(representing) 다른 값(value)을 내보낸다(emit). 이에 대한 좋은 예는
count
연산자(operator)이다.count
count
연산자(operator)는 publisher가.finished
완료(completion) 이벤트(event)를 보내면(sends), 업 스트림(upstream) publisher가 내보낸(emitted) 값의 개수를 나타내는 단일 숫자(single number)를 방출(emit)한다:이 예제를 확인하기 위해 다음 코드를 추가한다:
example(of: "count") { let publisher = ["A", "B", "C"].publisher //3개의 문자를 내보내는 publisher를 생성한다. publisher .print("publisher") .count() //업 스트림 publisher가 내보내는 값의 개수를 나타내는 단일 값을 방출한다. .sink(receiveValue: { print("I have \($0) items") }) //출력 .store(in: &subscriptions) //저장 }
playground를 실행(run)하고 콘솔(console)을 확인한다. 다음과 같은 결과를 볼 수 있다:
——— Example of: count ———
publisher: receive subscription: (["A", "B", "C"])
publisher: request unlimited
publisher: receive value: (A)
publisher: receive value: (B)
publisher: receive value: (C)
publisher: receive finished
I have 3 items예상대로 값(value)
3
은 업 스트림(upstream) publisher가.finished
완료(completion) 이벤트(event)를 보낸(sends) 경우에만 출력된다.contains
또 다른 유용한 연산자(operator)는
contains
이다. Swift 표준(standard) 라이브러리(library)에서 해당 연산자(counterpart)를 한 번 이상 사용해 봤을 것이다.contains
연산자(operator)는 지정된(specified) 값(value)이 업 스트림(upstream) publisher에서 방출된(emitted) 경우true
를 내보내고 구독(subscription)을 취소(cancel)하며, 방출된(emitted) 값(values)이 지정된(specified) 값과 같지 않으면false
를 내보낸다:다음을 playground에 추가하여
contains
을 사용해 본다:example(of: "contains") { let publisher = ["A", "B", "C", "D", "E"].publisher //5개의 서로 문자를 내보내는 publisher를 생성한다. let letter = "C" //포함 여부를 확인할 문자 publisher .print("publisher") .contains(letter) //해당 문자를 업 스트림 publisher에서 내보냈는지 확인한다. .sink(receiveValue: { contains in //contains 야부에 따라 적절한 메시지를 출력한다. print(contains ? "Publisher emitted \(letter)!" : "Publisher never emitted \(letter)!") }) .store(in: &subscriptions) //저장 }
playground를 실행(run)하고 콘솔(console)을 확인한다:
——— Example of: contains ———
publisher: receive subscription: (["A", "B", "C", "D", "E"])
publisher: request unlimited
publisher: receive value: (A)
publisher: receive value: (B)
publisher: receive value: (C)
publisher: receive cancel
Publisher emitted C!publisher가
C
를 내보냈다(emitted)는 메시지(message)를 받는다. 작업을 수행하는 데 필요한 만큼의 업 스트림(upstream) 값(values)만 소비(consumes)하므로contains
는 lazy하다.C
를 찾으면 구독(subscription)을 취소(cancels)하고 더 이상 값을 생성(produce)하지 않는다.다른 변형을 시도해 본다. 아래 행(line)을
let letter = "C"
다음으로 바꾼다:
let letter = "F"
그리고 playground를 실행(run)한다. 다음과 같은 결과를 볼 수 있을 것이다:
——— Example of: contains ———
publisher: receive subscription: (["A", "B", "C", "D", "E"])
publisher: request unlimited
publisher: receive value: (A)
publisher: receive value: (B)
publisher: receive value: (C)
publisher: receive value: (D)
publisher: receive value: (E)
publisher: receive finished
Publisher never emitted F!이 경우
contains
은 publisher가F
를 내보낼(publisher) 때까지 대기(waits for)한다. 그러나 publisher는F
를 내보내지(emitting) 않고 완료(finishes)되므로contains
는false
를 내보내고(emits) 적절한 메시지(message)를 출력한다.마지막으로, 제공(provide)하는 조건(predicate)에 일치(match for)하는 항목을 찾거나,
Comparable
을 준수(conform)하지 않는 방출(emitted) 값(value)의 존재를 확인해야할 때도 있다. 이러한 특정 사례에 대해서contains(where:)
을 사용할 수 있다.다음 예제를 playground에 추가한다:
example(of: "contains(where:)") { struct Person { let id: Int let name: String } //id와 name으로 구조체 Person을 정의한다. let people = [ (456, "Scott Gardner"), (123, "Shai Mishali"), (777, "Marin Todorov"), (214, "Florent Pillet") ] .map(Person.init) //생성 .publisher //Person의 다른 4개의 인스턴스를 내보내는 publisher를 생성한다. people .contains(where: { $0.id == 800 }) //id가 800인 Person이 있는지 확인한다. .sink(receiveValue: { contains in print(contains ? "Criteria matches!" : "Couldn't find a match for the criteria") //내보낸 결과에 따라 적절한 메시지를 출력한다. }) .store(in: &subscriptions) //저장 }
playground를 실행(run)하면 다음과 같은 결과(output)를 볼 수 있다:
——— Example of: contains(where:) ———
Couldn't find a match for the criteria내보낸(emitted) Person 중
id
가800
인 객체가 없기 때문에 예상대로 일치(matches)하는 항목을 찾지 못했다.다음으로,
contains(where:)
의 아래 구현을.contains(where: { $0.id == 800 })
다음과 같이 변경한다:
.contains(where: { $0.id == 800 || $0.name == "Marin Todorov" }
playground를 다시 실행(run)해서 콘솔(console)을 살펴본다:
——— Example of: contains(where:) ———
Criteria matches!이번에는 Marin이 실제로 목록(list)에 있는 Person 중 하나이기 때문에, 조건(predicate)과 일치(matching)하는 값을 찾았다.
allSatisfy
이제 두 개의 연산자만 남았다. 두 연산자 모두 Swift 표준(standard) 라이브러리(library)에 대응하는(counterpart) 컬렉션(collection) 메서드(methods)가 있다.
먼저
allSatisfy
로 시작한다. 이는 클로저(closure) 조건자(predicate)를 취하고, 업 스트림(upstream) publisher가 내보낸(emits) 모든 값(values)이 해당 조건(predicate)과 일치(match)하는지 여부를 나타내는 Boolean 값을 방출(emits)한다.따라서 업 스트림(upstream) publisher가
.finished
완료(completion) 이벤트(event)를 내보낼(emits) 때까지 기다린다(greedy):다음 예제를 playground에 추가하여 확인해 본다:
example(of: "allSatisfy") { let publisher = stride(from: 0, to: 5, by: 2).publisher //0 ~ 5 사이의 숫자를 2단계씩 내보내는 publisher를 생성한다(0, 2, 4). publisher .print("publisher") .allSatisfy { $0 % 2 == 0 } //'모든' 방출된 값이 짝수인지 확인한다. .sink(receiveValue: { allEven in print(allEven ? "All numbers are even" : "Something is odd...") //내보낸 결과에 따라 적절한 메시지를 출력한다. }) .store(in: &subscriptions) //저장 }
코드를 실행(run)하고, 콘솔(console)의 출력(output)을 확인한다:
——— Example of: allSatisfy ———
publisher: receive subscription: (Sequence)
publisher: request unlimited
publisher: receive value: (0)
publisher: receive value: (2)
publisher: receive value: (4)
publisher: receive finished
All numbers are even모든 값이 실제로 짝수(even)이므로, 업 스트림(upstream) publisher가
.finished
완료(completion)를 보내고, 연산자(operator)가true
를 방출(emits)한 후 적절한(appropriate) 메시지(message)를 출력한다.그러나 하나의 값(a single value)이라도 조건(the predicate condition)을 통과하지 못하면, 연산자(operator)는 즉시
false
를 내보내고(emit) 구독(subscription)을 취소(cancel)한다.아래 행(line)을
let publisher = stride(from: 0, to: 5, by: 2).publisher //0 ~ 5 사이의 숫자를 2단계씩 내보내는 publisher를 생성한다(0, 2, 4).
다음과 같이 바꾼다:
let publisher = stride(from: 0, to: 5, by: 1).publisher //0 ~ 5 사이의 숫자를 1단계씩 내보내는 publisher를 생성한다(0, 1, 2, 3, 4).
단순히
2
대신1
로,0
을5
로stride
단계(step)를 변경했다. 다시 한 번 playground를 실행(run)하고 콘솔(console)을 살펴본다:——— Example of: allSatisfy ———
publisher: receive subscription: (Sequence) publisher: request unlimited
publisher: receive value: (0)
publisher: receive value: (1)
publisher: receive cancel
Something is odd...이 경우
1
이 방출(emitted)되는 즉시 조건(predicate)을 더 이상 만족시키지 않으므로,allSatisfy
는false
를 내보내고(emits) 구독(subscription)을 취소(cancels)한다.reduce
이 장(chapter)의 마지막 연산자(operator)는
reduce
이다.reduce
연산자(operator)는 이 장(chapter)에서 다룬 나머지 연산자들과 약간 다르다. 특정(specific) 값(value)을 찾거나 publisher 전체를 질의(query)하지 않는다. 대신 업 스트림(upstream) publisher가 내보낸 값(emissions)을 기반으로 새로운 값을 반복적으로(iteratively) 축적(accumulate)할 수 있다.처음에는 헷갈릴(confusing) 수 있지만, 금방 이해할 수 있을 것이다. 가장 쉬운 방법은 도표(diagram)를 확인하는 것이다:
Combine의
reduce
연산자(operator)는 Swift 표준(standard) 라이브러리(library)의reduce(_:_)
및reduce(into:_:)
처럼(counterparts) 작동한다. 이는 시드 값(seed value)과 누산(accumulator) 클로저(closure)를 제공(provide)할 수 있다. 이 클로저(closure)는 시드 값부터 시작한 누적된(accumulated) 값(value)과 현재(current) 값(value)을 받는다(receives). 해당 클로저(closure)에서 새로운 누적(accumulated) 값(value)을 반환(return)한다. 연산자(operator)가.finished
완료(completion) 이벤트(event)를 수신(receives)하면 최종 누적(accumulated) 값(value)을 내보낸다(emits).위의 도표(diagram)의 경우, 다음과 같이 생각할 수 있다:
Seed value is 0
Receives 1, 0 + 1 = 1
Receives 3, 1 + 3 = 4
Receives 7, 4 + 7 = 11
Emits 11이 연산자(operator)를 더 잘 이해하기 위해 간단한 예제를 확인해 본다. playground에 다음을 추가한다:
example(of: "reduce") { let publisher = ["Hel", "lo", " ", "Wor", "ld", "!"].publisher //6개의 문자열을 내보내는 publisher를 생성한다. publisher .print("publisher") .reduce("") { accumulator, value in //seed와 함께 reduce를 사용한다. 내보낸 값을 추가하여 최종 문자열 결과를 만든다. accumulator + value } .sink(receiveValue: { print("Reduced into: \($0)") }) .store(in: &subscriptions) //저장 }
playground를 실행(run)하고, 콘솔(console)의 출력(output)을 살펴본다:
——— Example of: reduce ———
publisher: receive subscription: (["Hel", "lo", " ", "Wor", "ld", "!"])
publisher: request unlimited
publisher: receive value: (Hel)
publisher: receive value: (lo)
publisher: receive value: ( )
publisher: receive value: (Wor)
publisher: receive value: (ld)
publisher: receive value: (!)
publisher: receive finished
Reduced into: Hello World!누적된(accumulated) 결과(result)인
Hello World!
를 확인한다. 업 스트림(upstream) publisher가.finished
완료(completion) 이벤트(event)를 보낸(sent) 후에만 출력된다.reduce
의 두 번째 인수(argument)는 어떤 유형의 두 값(values)을 취하여 동일한 유형의 값(value)을 반환(returns)하는 클로저(closure)이다. Swift의+
는 해당 시그니처(signature)와 일치하는 함수(function)이다.따라서 깔끔하게 위의 구문을 줄일 수 있다. 아래 코드를
.reduce("") { accumulator, value in accumulator + value }
다음과 같이 바꾼다:
.reduce("", +)
다시 playground를 실행(run)해보면, 이전과 똑같이 작동한다.
Note: 3장(chapter), "Transforming Operators"에서
scan
에 대해 배웠기 때문에 이 연산자(operator)가 친숙하게 느껴질 수도 있다.scan
과reduce
는 기능(functionality)이 동일하지만, 주요 차이점은scan
이 모든 방출(emits)된 값에 대해 누적된(accumulated) 값(value)을 내보내는(emits) 반면,reduce
는 업 스트림(upstream) publisher가.finished
완료(completion) 이벤트(event)를 보내면(sends) 누적된 단일 값(single accumulated value)을 내보낸다(emits)는 것이다. 위의 예제에서reduce
대신scan
을 사용하여 직접 시도해 본다.Key points
- publishers는 컬렉션(collections)과 시퀀스(sequences)처럼 값(values)을 생성(produce)하므로, 실제로 시퀀스(sequences)이다.
min
과max
를 사용하여, publisher가 각각(respectively) 내보낸(emitted) 최소값(minimum) 또는 최대값(maximum)을 내보낼 수 있다.first
,last
,output(at:)
은 특정 인덱스(index)에서 내보낸(emitted) 값(value)을 찾으려는 경우 유용하다. 인덱스 범위(range of indices) 내에서 방출된(emitted) 값(values)을 찾으려면output(in:)
을 사용한다.first(where:)
과last(where:)
은 각각은 통과(through)시켜야하는 값(values)을 결정(determine)하기 위해 조건(predicate)을 사용한다.count
,contains
,allSatisfy
와 같은 연산자(operators)는 publisher가 방출한(emitted) 값(values)을 내보내지(emit) 않는다. 오히려 방출 된(emitted) 값(values)에 따라, 다른(different) 값(value)을 내보낸다.contains(where:)
는 publisher가 지정된 값을 포함(contains)하는지 확인하기(determine) 위해 조건(predicate)을 사용한다.- 방출된(emitted) 값(values)을 단일 값(single value)으로 누적(accumulate)하려면
reduce
를 사용한다.
'Raywenderlich > Combine: Asynchronous Programming' 카테고리의 다른 글
Chapter 9: Networking (0) 2020.08.13 Chapter 8: In Practice: Project "Collage" (0) 2020.08.11 Chapter 6: Time Manipulation Operators (0) 2020.07.27 Chapter 5: Combining Operators (0) 2020.07.20 Chapter 4: FilteringOperators (0) 2020.07.17