티스토리 뷰

728x90

각 Operator는 publisher를 반환합니다.

publisher는 upstream 이벤트를 받고 조작합니다.

그러고나서, 조작된 이벤트들을 사용자에게 downstream으로 보냅니다.

 

Collecting Values

 

collect()

각각의 value들을 한 배열안에 넣습니다.

collect()

1. collect() 안썼을시 
var subscriptions = Set<AnyCancellable>()

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

/*
 ——— Example of: collect ———
 A
 B
 C
 D
 E
 finished
*/

2. collect() 사용
var subscriptions = Set<AnyCancellable>()

example(of: "collect") {
    ["A", "B", "C", "D", "E"].publisher
        .collect()   // <- collect()추가
        .sink(receiveCompletion: { print($0) }, receiveValue: { print($0) })
        .store(in: &subscriptions)
}
/*
——— Example of: collect ———
["A", "B", "C", "D", "E"]
finished
*/


3. collect(number) 
괄호안에 숫자가 있을 시, collect() 괄호 안에 숫자만큼 요소들을 넣습니다.

var subscriptions = Set<AnyCancellable>()

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

/*
 ——— Example of: collect ———
 ["A", "B"]
 ["C", "D"]
 ["E"]
 finished
*/

 

Mapping values

 

map(_:)

publisher에서 방출된 값으로 작동한다는 것을 제외하면 swift의 map과 같습니다.

map(_:)

 

var subscriptions = Set<AnyCancellable>()

example(of: "map") {
    let formatter = NumberFormatter()
    formatter.numberStyle = .spellOut
    
    [12, 456, 7].publisher
        .map {
            formatter.string(from: NSNumber(integerLiteral: $0)) ?? ""
        }
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

/*
 ——— Example of: map ———
 twelve
 four hundred fifty-six
 seven
*/

 

Mapping key paths

아래는 사분면 에 관한 예시 입니다.
var subscriptions = Set<AnyCancellable>()

example(of: "mapping key paths") {
    let publisher = PassthroughSubject<Coordinate, Never>()
    
    publisher
        .map(\.x, \.y) // Keypath
        .sink(receiveValue: { x, y in
            print("좌표 (\(x), \(y)) 는", quadrantOf(x: x, y: y), "사분면 입니다.")
        })
        .store(in: &subscriptions)
    
    publisher.send(Coordinate(x: 10, y: -8))
    publisher.send(Coordinate(x: 0, y: 5))
}

/*
 ——— Example of: mapping key paths ———
 좌표 (10, -8) 는 4 사분면 입니다.
 좌표 (0, 5) 는 boundary(경계선) 사분면 입니다.
*/

 

tryMap(_:)

error throw를 tryMap을 사용해서 error downstream을 방출합니다.

아래는 존재하지 않는 디렉토리 이름을 설정해 에러를 내보는 예시 입니다.
var subscriptions = Set<AnyCancellable>()

example(of: "tryMap") {
    Just("Directory name that does not exist")
        .tryMap { try FileManager.default.contentsOfDirectory(atPath: $0) }
        .sink(receiveCompletion: { print($0) }, receiveValue: { print($0) })
        .store(in: &subscriptions)
}

/*
 ——— Example of: tryMap ———
 failure(Error Domain=NSCocoaErrorDomain Code=260 "The folder “Directory name that does not exist” doesn’t exist." UserInfo={NSUserStringVariant=(
     Folder
 ), NSFilePath=Directory name that does not exist, NSUnderlyingError=0x6000030ea130 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}})
*/

 

Flattening publishers

 

flatMap(maxPublishers:_:)

여러개의 publisher upstream -> single downstream으로 변환 해줍니다.
var subscriptions = Set<AnyCancellable>()

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] // Hello
        .publisher
        .collect()
        .flatMap(decode) // 질문: 함수 호출하려면 decode()를 해줘야 하지않나??
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

/*
 ——— Example of: flatMap ———
 Hello
 */


flatMap(maxPublishers:)

위 다이어그램에 대한 예시는 Chapter19에서 자세히 다룰 예정.

 

Replacing upstream output

 

replaceNil(with:)

upstream publisher에서 받아온nil값을 non-nil 값으로 변환 해줍니다.
var subscriptions = Set<AnyCancellable>()

example(of: "replaceNil") {
    ["A", nil, "C"].publisher
        .eraseToAnyPublisher()  // 이걸 해주지 않으면 Optional이 해제되지 않아 값에 Optional이 걸려있음 (Combine 버그..)
        .replaceNil(with: "BBB") // nil 값이 있으면 "BBB"로 바꿈
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

/*
 ——— Example of: replaceNil ———
 A
 BBB
 C
*/

 


replaceNil nil-coalescing (??) 
nil을 결과로 받을 수 있음 nil을 결과로 받을 수 없음


var subscriptions = Set<AnyCancellable>()

example(of: "replaceNil") {
    ["A", nil, "C"].publisher
        .eraseToAnyPublisher()
        .replaceNil(with: "BBB" as String?) // 전체 Optional
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

/*
——— Example of: replaceNil ———
Optional("A")
Optional("BBB")
Optional("C")
*/

 

replaceEmpty(with:)

upstream에서 값이 방출되지 않고 completion되면, value를 하나 넣어줍니다.
replaceEmpty(with:)
var subscriptions = Set<AnyCancellable>()

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


replaceEmpty(with:) 사용

var subscriptions = Set<AnyCancellable>()

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

 

Incrementally transforming output

 

scan(_:_:)

Swift의 고차함수 reduce()와 비슷합니다.
scan(초기값) { $0(이전값) + $1(다음값) } 
점점 증가시킵니다.

scan(_:_:)

var subscriptions = Set<AnyCancellable>()

example(of: "scan") {
    var dailyGainLoss: Int { .random(in: -10...10) }
    
    let august2019 = (0..<22)
        .map { _ in dailyGainLoss }
        .publisher
    
    august2019
        .scan(50) { latest, current in // 초기값 = 50
            max(0, latest + current)
        }
        .sink(receiveValue: { _ in })
        .store(in: &subscriptions)
}

 

 

Challenge: transforming operators를 활용해 전화번호 만들기

더보기

문제

1. Convert the input to numbers — use the convert function, which will return nil if it cannot convert the input to an integer.

-> input을 숫자로 변환 — input을 정수로 변환할 수 없는 경우 nil을 반환하는 convert 함수를 사용합니다.

2. If the previous operator returns nil, replace it with a 0.

-> 이전 연산자가 nil을 반환하면 0으로 바꿉니다.

3. Collect ten values at a time, which correspond to the three-digit area code and seven-digit phone number format used in the United States.

-> 미국에서 사용되는 3자리 지역 코드 및 7자리 전화번호 형식에 해당하는 10개의 값을 한 번에 수집(collect)합니다.


4. Format the collected string value to match the format of the phone numbers in the contacts dictionary — use the provided format function.

-> contacts에 있는 전화번호의 형식과 일치하도록 수집된 문자열 값의 형식을 지정합니다. 제공된 형식 기능을 사용합니다.

5. “Dial” the input received from the previous operator — use the provided dial function.

-> 이전 연산자로부터 받은 input "다이얼" - 제공된 다이얼 기능을 사용합니다.

input
        .map(convert) // String -> Int 변환 >> (문자입력시) keyMap을 통해 문자에 포함되는 숫자로 변환
        .replaceNil(with: 0) // Optional 바인딩 처리 후, 변환할 수 없는것 (nil) -> 0으로 대체
        .collect(10) // 한 배열에 10개 요소 담기
        .map(format) // Int -> String >> String 통합 >> 인덱스 3번째, 7번째 자리에 "-" 추가
        .map(dial) // contacts[phoneNumber] 키 값으로 접근해 value(사람이름) 반환
        .sink(receiveCompletion: { print($0) },
              receiveValue: { print($0) })

/*
——— Example of: Create a phone number lookup ———
Contact not found for 000-123-4567
Dialing Marin (408-555-4321)...
Dialing Shai (212-555-3434)...
*/

 

배운것

1. 함수안에 함수를 사용할때 소괄호로 호출을 따로 안해줘도 된다. (예 - .map(convert)) 

    -> 일급객체기 때문

2. 연속해서 함수의 조합들을 이용해 구현하는 방법이 꽤 직관적이고 코드가 간결해져 좋았다. 함수형 프로그래밍 체험한듯

 

 

728x90