Skip to content

Latest commit

 

History

History
473 lines (358 loc) · 11.2 KB

4.Filtering Operators.md

File metadata and controls

473 lines (358 loc) · 11.2 KB

4. Filtering Operators

Filtering basics

filter

Bool타입을 반환하는 클로저를 사용하고 조건과 일치하는 값만 전달함

example(of: "filter") {
    let numbers = (1...10).publisher
    numbers
        .filter { $0.isMultiple(of: 3) }
        .sink(receiveValue: { n in
            print("\(n) is a multiple of 3!")
        })
        .store(in: &subscriptions)
}
// print
——— Example of: filter ———
3 is a multiple of 3!
6 is a multiple of 3!
9 is a multiple of 3!

removeDuplicates()

반복되는 동일한 값을 무시할 수 있음

Equtable을 따르는 모든 값에 자동으로 작동함

따르지 않는다면 클로저를 제공해주고 조건을 넣어줌

example(of: "removeDuplicates") {
    [1,2,2,3,3,3,4,4,3,2,2,1,5]
        .publisher
        .removeDuplicates()
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}
// print
——— Example of: removeDuplicates ———
1
2
3
4
3
2
1
5

Compacting and ignoring

게시자가 optional값을 방출하는 경우가 많습니다.

스위프트 표준 라이브러리인 compactMap과 같은 이름을 가진 연산자가 있습니다.

compactMap

표준라이브러리 기능과같이 nil값을 걸러줍니다.

example(of: "compactMap") {
    let strings = ["a", "1.24", "3",
                   "def", "45", "0.23"].publisher
    strings
        .compactMap { Float($0) }
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}
// print
——— Example of: compactMap ———
1.24
3.0
45.0
0.23
[1, 2, nil, 4].publisher // Publisher<[Int?], Never>
        .compactMap { $0 } // Publisher<[Int], Never>
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
// print
1
2
4

ignoreOutput()

실제 값을 무시하면서 방출이 끝낫다는 것을 알고 싶을 때 이 연산자를 사용할 수 있습니다.

어떠한 값이 방출되는지 얼마나 많은 값이 무시되는지 중요하지않고 completion이벤트만 전달합니다.

example(of: "ignoreOutput") {
    let numbers = (1...10_000).publisher
    numbers
        .ignoreOutput()
        .sink(receiveCompletion: { print("Completed with: \($0)") },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
}
// print
——— Example of: ignoreOutput ———
Completed with: finished

Finding values

first(where: )

이름에서 알수 있듯이, 조건에 맞는 첫번째 값을 방출함

lazy하기 떄문에 일치하는 값을 찾을 때까지 필요한 만큼만 값을 취함

일치하는 항목을 찾는 즉시 구독을 취소하고 완료합니다.

example(of: "first(where:)") {
    let numbers = (1...9).publisher
    numbers
        .first(where: { $0 % 2 == 0 })
        .sink(receiveCompletion: { print("Completed with: \($0)") },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
}
// print
——— Example of: first(where:) ———
2
Completed with: finished

업스트림구독은 짝수2를 찾고나서도 계속 방출하는가?

print를 사용해서 게시자를 디버그해보면

——— Example of: first(where:) ———
number:: receive subscription: (1...9)
number:: request unlimited
number:: receive value: (1)
number:: receive value: (2)
number:: receive cancel
2
Completed with: finished

값을 찾는 즉시 구독을 취소하고 더이상 방출되지 않는걸 알 수 있습니다.

(구독이 취소된 이후에 finished가 됨)

last(where: )

제공된 조건과 일치하는 마지막 값을 찾는것이 목적인 연산자입니다.

일치하는 값을 찾기위해 방출 값을 완료할 때 까지 기다려야합니다.

(끝까지 탐색을해야 알 수 있기때문)

example(of: "last(where:)") {
    let numbers = (1...9).publisher
    numbers
        .print()
        .last(where: { $0 % 2 == 0 })
        .sink(receiveCompletion: { print("Completed with: \($0)") },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
}
// print
——— Example of: last(where:) ———
receive subscription: (1...9)
request unlimited
receive value: (1)
receive value: (2)
receive value: (3)
receive value: (4)
receive value: (5)
receive value: (6)
receive value: (7)
receive value: (8)
receive value: (9)
receive finished
8
Completed with: finished

다른 예시

example(of: "last(where:)") {
    let numbers = PassthroughSubject<Int, Never>()
    numbers
        .print()
        .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)
    // 완료해야 동작함
    numbers.send(completion: .finished)
}
// print
——— Example of: last(where:) ———
receive subscription: (PassthroughSubject)
request unlimited
receive value: (1)
receive value: (2)
receive value: (3)
receive value: (4)
receive value: (5)
// numbers.send(completion: .finished) 추가시 밑에 출력가능
receive finished
4
Completed with: finished

send에 값만 담아서 전달할 경우 아무결과를 볼 수 없지만

마지막에 completion을 전달해줌으로써 전체범위를 알고 연산자가 동작함


Dropping values

두번째 게시자가 게시를 시작할 때까지 한 게시자의 값을 무시하거나, 스트림 시작시 일정 양의 값을 무시하려는 경우 이 연산자를 사용할 수 있습니다.

연관된 3개의 연산자가 존재하고 처음으로는 dropFirst를 볼것입니다.

dropFirst

게시자는 count파라미터로 넣어준 숫자만큼 값을 무시한 후 값을 전달합니다.

디폴트는 1입니다.

example(of: "dropFirst") {
    let numbers = (1...10).publisher
    numbers
        .dropFirst(8)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}
// print
——— Example of: dropFirst ———
9
10

위의 코드에서 8개를 건너뛰고 9부터 값을 받아오는걸 볼 수 있습니다.

drop(while:)

조건이 false되기 전까지 방출한 값을 무시합니다.(true이면 건너뜀)

조건이 false가 되는순간 값이 연산자를 통해 흐르기 시작합니다.

example(of: "drop(while:)") {
    let numbers = (1...10).publisher
    numbers
        .drop(while: { $0 % 5 != 0 })
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}
// print
——— Example of: drop(while:) ———
5
6
7
8
9
10

위의코드에서 5로 나눌 수 있는 첫번째 값을 기다리고 충족하는 즉시 값이 흐릅니다.

filter와 drop의차이점

둘다 어떤 값이 방출되는지 제어하는 클로저를 사용함

첫번째로 filter는 클로저에서 true를 반환하면 값을 통과 할수 있고, drop은 true를 반환하는 동안 값을 건너 뜁니다.

두번째로 filter는 업스트림의 게시자를 정지시키지 않는다는것입니다. 필터의 조건이 true인 후에도 값은 여전히 이 값을 통과시키겠는가? 질문해야합니다. 반대로, drop은 조건이 충족된 후에 다시 실행되지 않습니다.

example(of: "drop(while:)") {
    let numbers = (1...10).publisher
    numbers
        .drop(while: {
          print("x")
          return $0 % 5 != 0
        })
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}
// print
——— Example of: drop(while:) ———
x
x
x
x
x
5
6
7
8
9
10

위의코드처럼 조건이 충족되자마자 drop연산자는 다시 호출되지않습니다.

drop(untilOutputFrom: )

사용자가 버튼을 계속 tap하고있고 isReady게시자가 어떤 값을 낼 때까지 모든 tap을 무시하려할 때 유용하게 쓰이는 연산자입니다.

두번째 게시자가 값을 방출하기 시작할 때까지 게시자가 방출한 값을 건너뜁니다.

image

example(of: "drop(untilOutputFrom:)") {
    let isReady = PassthroughSubject<Void, Never>()
    let taps = PassthroughSubject<Int, Never>()
    taps
        .drop(untilOutputFrom: isReady)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
    
    (1...5).forEach { n in
        taps.send(n)
        if n == 3 {
            isReady.send()
        }
    }
}
// print
——— Example of: drop(untilOutputFrom:) ———
4
5

taps게시자는 isReady게시자가 값을 방출할 때까지 taps에 들어오는 값을 무시합니다. (이떄 drop업스트림은 방출값을 받고, drop에서 무시하기때문에 다운스트림부터는 값을 무시함)


Limiting values

알수 없는 양의 값을 방출할 수 있지만, 단일 방출만 원하고 나머지는 신경 쓰지않는 요청을 고려할 때 유용한 기능입니다.

생김새는 이전에 본 drop그룹과 비슷합니다. 어떤 조건이 충족될 때까지 값을 버리는 대신 prefix연산자는 해당 조건이 충족 될때 까지 값을 취합니다.

prefix

넣어준 숫자 갯수까지만 값을 취한다음 완료합니다.

example(of: "prefix") {
    let numbers = (1...10).publisher
    numbers
        .prefix(2)
        .sink(receiveCompletion: { print("Completed with: \($0)") },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
}
// print

——— Example of: prefix ———
1
2
Completed with: finished

prefix(while: )

first(where: )과 마찬가지로 lazy연산자입니다.

필요한 만큼만 값을 가져온다음 종료됩니다.

클로저의 값이 true인경우 값을 허용합니다.

false가 되면 게시자는 완료됩니다.

example(of: "prefix(while:)") {
    let numbers = (1...10).publisher
    
    numbers
        .prefix(while: { $0 < 3 })
        .sink(receiveCompletion: { print("Completed with: \($0)") },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
}
// print
——— Example of: prefix(while:) ———
1
2
Completed with: finished

조건이 false가되는 3값이 방출되는 즉시 게시자가 완료됩니다.

prefix(untilOutputFrom: )

두번째 게시자가 방출 될때까지 값을 취합니다.

image

example(of: "prefix(untilOutputFrom:)") {
    let isReady = PassthroughSubject<Void, Never>()
    let taps = PassthroughSubject<Int, Never>()
    taps
        .prefix(untilOutputFrom: isReady)
        .sink(receiveCompletion: { print("Completed with: \($0)") },
              receiveValue: { print($0) })
        .store(in: &subscriptions)

    (1...5).forEach { n in
        taps.send(n)
        if n == 2 {
            isReady.send()
        }
    }
}
// print
——— Example of: prefix(untilOutputFrom:) ———
1
2
Completed with: finished

isReady가 적어도 하나의 값을 방출 할 때까지 taps이벤트를 받을 수 있습니다.

isReady가 값을 방출한 후 taps의 이벤트를 받을 수 없고 완료됩니다.