티스토리 뷰
각 Operator는 publisher를 반환합니다.
publisher는 upstream 이벤트를 받고 조작합니다.
그러고나서, 조작된 이벤트들을 사용자에게 downstream으로 보냅니다.
Collecting Values
collect()
각각의 value들을 한 배열안에 넣습니다.
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과 같습니다.
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 */
위 다이어그램에 대한 예시는 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를 하나 넣어줍니다.
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(다음값) }
점점 증가시킵니다.
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. 연속해서 함수의 조합들을 이용해 구현하는 방법이 꽤 직관적이고 코드가 간결해져 좋았다. 함수형 프로그래밍 체험한듯
'iOS' 카테고리의 다른 글
[Swift 알고리즘] - 자연수 뒤집어 배열로 만들기 (Programmers) (0) | 2022.11.05 |
---|---|
[Combine] Chapter4: Filtering Operators (1) | 2022.11.03 |
[Combine] 개념 (0) | 2022.10.13 |
[Swift] fatal Error (4) | 2022.09.25 |
[Swift] 고급연산자 (Advanced Operators) - 비트연산자 (0) | 2022.09.22 |
- Total
- Today
- Yesterday
- Class
- 원티드 프리온보딩
- removeLast()
- swift property
- swift (programmers)
- Swift 프로그래머스
- ios
- 2023년 회고
- RTCCameraVideoCapturer
- Swift ModernRIBs
- Swift init
- Swift joined()
- Swift
- iOS error
- Swift Error Handling
- Combine: Asynchronous Programming with Swift
- Swift 알고리즘
- Swift final
- Swift inout
- swift 고차함수
- Swift joined
- CS 네트워크
- swift programmers
- Swift 프로퍼티
- Swift 내림차순
- Swift Leetcode
- Swift RIBs
- RIBs tutorial
- swift protocol
- swift reduce
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |