Skip to content

Latest commit

 

History

History
347 lines (256 loc) · 9.88 KB

3.Transforming Operators.md

File metadata and controls

347 lines (256 loc) · 9.88 KB

Transforming Operators

Operators are publishers

각 컴바인 연산자는 publisher를 반환합니다.

일반적으로 게시자는 업스트림 이벤트를 받고, 조작한 다음 조작한 이벤트를 소비자에게 다운스트림으로 보냅니다.

Collecting values

게시자는 개별값 또는 컬래션 값을 방출할 수 있습니다.

collect()

이 연산자는 개별 값 스트림을 단일 배열로 변환하는 편리한 방법을 제공합니다.

image

example(of: "collect") {
    print("---none---")
    ["A", "B", "C", "D", "E"].publisher
        .sink(receiveCompletion: { print($0) },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
    print("---collect()---")
    ["A", "B", "C", "D", "E"].publisher
        .collect()
        .sink(receiveCompletion: { print($0) },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
    print("---collect(2)---")
    ["A", "B", "C", "D", "E"].publisher
        .collect(2)
        .sink(receiveCompletion: { print($0) },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
}

// print
——— Example of: collect ———
---none---
A
B
C
D
E
finished
---collect()---
["A", "B", "C", "D", "E"]
finished
---collect(2)---
["A", "B"]
["C", "D"]
["E"]
finished

Mapping values

이러한 값을 어떤 식으로든 변환시키려 하는경우가 종종 있습니다.

컴바인은 이러한 목적을 위해 여러 매핑 연산자를 제공합니다.

map(_:)

스위프트의 표준 map과 동일하게 작동하지만, publisher로 방출되는 값이라는 점만 다릅니다.

image

example(of: "map") {
    let formatter = NumberFormatter()
    formatter.numberStyle = .spellOut
    [123, 4, 56].publisher
        .map {
            formatter.string(for: NSNumber(integerLiteral: $0)) ?? ""
        }
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}
// print
——— Example of: map ———
백이십삼
사
오십육

Mapping key paths

map연산자에는 키경로를 이용하여 1, 2 또는 3개의 프로퍼티로 매핑할 수 있는 세가지 버전이 포함되어있습니다.

map<T>(_:)

map<T0, T1>(_: _:)

map<T0, T1, T2>(_: _: _:)

struct Coordinate {
    var x: Int
    var y: Int
}
func quadrantOf(x: Int, y: Int) -> String {
    return "어느 사분면"
}
example(of: "mapping key paths") {
    let publisher = PassthroughSubject<Coordinate, Never>()
    publisher
        .map(\.x, \.y)
        .sink(receiveValue: { x, y in
            print(
                "The coordinate at (\(x), \(y)) is in quadrant",
                quadrantOf(x: x, y: y)
            )
        })
        .store(in: &subscriptions)
    publisher.send(Coordinate(x: 10, y: -8))
    publisher.send(Coordinate(x: 0, y: 5))
}
// print
——— Example of: mapping key paths ———
The coordinate at (10, -8) is in quadrant 어느 사분면
The coordinate at (0, 5) is in quadrant 어느 사분면

이 예제에서는 키 경로를 통해서 두개의 프로퍼티로 매핑되는 map을 사용합니다.

tryMap(_:)

map을 포함한 몇몇 연산자에는 throwing을 받을 수 있는 try접두사가 있는 연산자가 있습니다.

만약 오류가 발생하면, 연산자는 해당 오류를 다운스트림으로 내보냅니다.

func convert(num: String) throws -> Int {
    guard let number = Int(num) else {
        throw NSError(domain: "변환이 불가능한 문자열입니다.", code: 0)
    }
    return number
}
example(of: "tryMap") {
    Just("???")
        .tryMap { try convert(num: $0) }
        .sink(receiveCompletion: { print("completion:", $0) },
              receiveValue: { print("value:", $0) })
        .store(in: &subscriptions)
}
// print
——— Example of: tryMap ———
completion: failure(Error Domain=변환이 불가능한 문자열입니다. Code=0 "(null)")

오류가 발생하면 completion failure로 종료됨


Flattening publisher

flatMap(maxPublisher: _:)

이 연산자는 여러 업스트림 publisher를 단일 다운스트림 publisher로 평평하게 하거나, 더 구체적으로는 이 publisher의 배출을 평평하게 만듭니다.

flatMap에 의해 반환된 게시자는 수신하는 업스트림 게시자와 같은 타입이 아니며 종종 그렇지 않습니다.

컴바인의 flatMap에 대한 일반적인 사용예시는 한 게시자가 방출한 요소를 게시자를 반환하는 메서드로 전달하고 궁극적으로 두번째 게시자가 방출한 요소를 구독하려는 경우입니다. (요약: Puiblisher를 반환하는 함수를 전달하고, 그 연산된 결과를 이용해서 구독하려는 경우)

example(of: "flatMap") {
    func decode(_ codes: [Int]) -> AnyPublisher<String, Never> {
        Just( codes
            .compactMap { code in
                guard (32...255).contains(code) else { return nil }
                return String(UnicodeScalar(code) ?? " ")
            }
            .joined() )
        .eraseToAnyPublisher()
    }
    [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]
        .publisher
        .collect()
        .flatMap(decode)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}
// print
——— Example of: flatMap ———
Hello, World!

flatMap은 수신된 모든 publisher의 출력을 단일 publisher로 평평하게 만듭니다.

이 것은 메모리 문제를 일으킬 수 있습니다.

다운스트림으로 방출되는 단일 publisher를 업데이트하기 위해서 당신이 보내는 만큼의 publisher를 버퍼링 하기때문입니다.

image

flatMap은 p1,p2,p3 3개의 publisher를 받습니다.

이 게시자 각각은 value프로퍼티를 가지고있습니다.

flatMap은 P1, P2의 publisher의 값인 value를 방출하지만, max가 2이기 때문에 P3은 무시합니다.


Replacing upstream output

컴바인에는 항상 값을 전달하고자 할 때 사용할 수 있는 연산자도 포함됩니다.

replaceNill(with: )

옵셔널값을 받고 nil을 지정한 값으로 바꿉니다.

example(of: "replaceNil") {
    ["A", nil, "C"].publisher
        .eraseToAnyPublisher()
        .replaceNil(with: "-")
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
    
    ["A", nil, "C"].publisher
        .replaceNil(with: "-")
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}
// print
——— Example of: replaceNil ———
A
-
C
Optional("A")
Optional("-")
Optional("C")

혼동을 일으킬 수 있는 점이 있으므로 잘못 선택할 수 있습니다.

완전히 언래핑되는 대신에 Optional<String> 타입이 남습니다.

위의 코드는 eraseToAnyPublisher()르를 사용하여 해당 버그를 해결합니다.

이문제는 이 링크에서 확일 할 수 있습니다. swift포럼

주의

rx처럼 nil을 대체하는 연산자를 사용하면 반환타입이 옵셔널이 제거된 형태로 나오길 기대하지만

위의 코드를 사용하면 Optional형태가 나옴 (버그인듯)

업스트림에 eraseToAnyPublisher를 사용하여 제거해야함

replaceEmpty(with:)

publisher가 값을 방출하지않고 완료되면 값을 바꾸거나 실제로 값을 삽입 할 수 있습니다.

아래의 마블 다이어그램을 보면, 게시자는 아무것도 방출하지않고 완료하고 그 시점에서 replaceEmpty(with:)연산자는 값을 삽입하여 다운 스트림으로 publish합니다

image

example(of: "replaceEmpty(with:)") {
    let empty = Empty<Int, Never>()
    empty
        .sink(receiveCompletion: { print($0) },
              receiveValue: { print($0) })
        .store(in: &subscriptions)
    
    empty
        .replaceEmpty(with: 1)
        .sink(receiveCompletion: { print("replace:", $0) },
              receiveValue: { print("replace:", $0) })
        .store(in: &subscriptions)
}
// print

——— Example of: replaceEmpty(with:) ———
finished
replace: 1
replace: finished

Empty publisher타입을 사용해서 즉시 .finished 완료이벤트를 방출하는 publisher를 만들 수 있습니다.

또한 completeImmediately프로퍼티에 false를 설정해서 아무것도 방출하지 않도록 할 수 있습니다. 기본값은 true입니다.(false로 설정하면 Neverpublisher와 같음 )


Incrementally transforming output

컴바인에는 업스트림 게시자로부터 받은 값을 조작할 수 있는 몇가지 트릭이 있습니다.

scan(_: _:)

업스트림 게시자가 클로저에서 방출한 현재 값과 해당 클로저에서 반환한 마지막값을 제공합니다.

다이어그램에서 scan은 0을 저장하고 시작합니다.

게시자로부터 값을 수신할때마다 이전에 저장된 값에 추가한다음 결과를 저장하고 방출합니다.

image

(초기값을 지정하고 현재값과 이전값을 연산한 결과를 방출함)

example(of: "scan") {
    var dailyGainLoss: Int { -2 }
    let august2019 = (0..<22)
        .map { _ in dailyGainLoss }
        .publisher
    august2019
        .scan(50) { latest, current in
            max(0, latest + current)
        }
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}
//print
——— Example of: scan ———
48
46
44
42
40
38
36
34
32
30