티스토리 뷰

iOS

[Combine] Chapter5: Combining Operators

Peppo 2022. 11. 10. 20:29
728x90

오늘은 이어서 Combining 연산자에 대해 공부해 보겠습니다.

 

Prepending

upstream에서 방출한 값 앞에 추가해서 보내는 용도로 사용합니다.

 

prepend(Output)

publisher에서 방출 되는 이벤트 이전에 값을 넣어줄 때 사용합니다.
prepend(Output)

var subscriptions = Set<AnyCancellable>()

example(of: "prepend(output)") {
    
    let publisher = [3, 4].publisher
    
    publisher
        .prepend(1, 2)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

/*
 ——— Example of: prepend(output) ———
 1
 2
 3
 4
*/​


아래처럼 음수 를 추가해도 순서대로 나열 됩니다.

var subscriptions = Set<AnyCancellable>()

example(of: "prepend(output)") {
    
    let publisher = [3, 4].publisher
    
    publisher
        .prepend(1, 2)
        .prepend(-1, -2)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

/*
 ——— Example of: prepend(output) ———
 -1
 -2
 1
 2
 3
 4
*/



prepend(Sequence)

prepend(Sequence)



(Output)과 유사하며, Sequence를 준수합니다.

var subscriptions = Set<AnyCancellable>()

example(of: "prepend(Sequence)") {
    
    let publisher = [5, 6, 7].publisher
    
    publisher
        .prepend([3, 4])
        .prepend(stride(from: 6, to: 11, by: 2))
        .prepend(Set(1...2))
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

/*
 ——— Example of: prepend(Sequence) ———
 2 또는 1
 1 또는 2
 6
 8
 10
 3
 4
 5
 6
 7
 */


* Set은 정렬되지 않기 때문에 순서가 랜덤으로 나옵니다.


prepend(Publisher)


두개의 다른 publisher들을 하나로 합쳐서 나열할 경우에 사용합니다.
(
publisher2가 오리지날 publisher 이전에 방출됩니다.)

prepend(Publisher)

 

// #1

var subscriptions = Set<AnyCancellable>()

example(of: "prepend(Publisher)") {
    
    let publisher1 = [3, 4].publisher
    let publisher2 = [1, 2].publisher
    
    publisher1
        .prepend(publisher2)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

/*
 ——— Example of: prepend(Publisher) ———
 // publisher2
 1
 2
 // publisher1
 3
 4
*/


// #2

var subscriptions = Set<AnyCancellable>()

example(of: "prepend(Publisher) #2") {

    let publisher1 = [3, 4].publisher
    let publisher2 = PassthroughSubject<Int, Never>()

    publisher1
        .prepend(publisher2)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
    
    publisher2.send(1)
    publisher2.send(2)
    publisher2.send(completion: .finished)
}

/*
 ——— Example of: prepend(Publisher) #2 ———
 1
 2
 3
 4
 */


* publihser2 값 방출이 끝났다는걸 알려주려면 complete 시켜줘야 합니다.
   안해주면 publisher1 값만 나오게 됩니다.

 

 

Appending

 

append(Output)


prepend와 반대의 개념으로 생각하면 됩니다.
prepend가 이벤트를 방출할때 upstream 앞에 붙였다면,
append는 뒤에 붙입니다.

append(Output)

// #1

var subscriptions = Set<AnyCancellable>()

example(of: "append(Output)") {
    
    let publisher = [1].publisher
    
    publisher
        .append(2, 3)
        .append(4)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}
/*
 ——— Example of: append(Output) ———
 1
 2
 3
 4
 */
 
 
 // #2
 
var subscriptions = Set<AnyCancellable>()

example(of: "append(Output)") {
    
    let publisher = PassthroughSubject<Int, Never>()
    
    publisher
        .append(3, 4)
        .append(5)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
    
    publisher.send(1)
    publisher.send(2)
    publisher.send(completion: .finished)  // <- 추가 해줘야 append operator가 동작합니다.
}

/*
 ——— Example of: append(Output) ———
 1
 2
 3
 4
 5
 */


append(Sequence)

append(Sequence)

 

var subscriptions = Set<AnyCancellable>()

example(of: "append(Sequence)") {
    
    let publisher = [1, 2, 3].publisher
    
    publisher
        .append([4, 5])
        .append(Set([6, 7]))
        .append(stride(from: 8, to: 11, by: 2)) // 8부터 11까지 2씩 증가
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

/*
 ——— Example of: append(Sequence) ———
 1
 2
 3
 4
 5
 6 또는 7
 7 또는 6
 8
 10
 */

 

append(Publisher)

append(Publisher)

 

var subscriptions = Set<AnyCancellable>()

example(of: "append(Publisher)") {
    
    let publisher1 = [1, 2].publisher
    let publisher2 = [3, 4].publisher
    
    publisher1
        .append(publisher2)
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}

/*
 ——— Example of: append(Publisher) ———
 1
 2
 3
 4
 */

 

 

 

Advanced combining

 

switchToLatest()

새로운 구독이 들어오면 이전 publisher를 무시하고, 최신 publisher로 전환 할 수 있습니다.

switchToLatest

var subscriptions = Set<AnyCancellable>()

example(of: "switchToLatest") {

    let publisher1 = PassthroughSubject<Int, Never>()
    let publisher2 = PassthroughSubject<Int, Never>()
    let publisher3 = PassthroughSubject<Int, Never>()

    let publishers = PassthroughSubject<PassthroughSubject<Int, Never>, Never>()

    publishers
        .switchToLatest() // 최신 publisher로 switch
        .sink(receiveCompletion: { _ in print("Completed!")},
              receiveValue: { print($0) }
        )
        .store(in: &subscriptions)

    publishers.send(publisher1)
    publisher1.send(1)
    publisher1.send(2)

    publishers.send(publisher2) // publisher2를 구독하므로
    publisher1.send(3) // publisher1의 해당 이벤트는 무시됨
    publisher2.send(4)
    publisher2.send(5)

    publishers.send(publisher3) // publisher3를 구독하므로
    publisher2.send(6) // publisher2의 해당 이벤트는 무시됨
    publisher3.send(7)
    publisher3.send(8)
    publisher3.send(9)

    publisher3.send(completion: .finished)
    publishers.send(completion: .finished)
}

/*
 ——— Example of: switchToLatest ———
 1
 2
 4
 5
 7
 8
 9
 Completed!
 */

 

아래는 네트워크 요청을 하는 버튼을 누른후 바로 또 네트워크 요청을 하게 되는 경우 어떻게 처리되는지 보여주는 예시 입니다.

var subscriptions = Set<AnyCancellable>()

example(of: "switchToLatest - Network Request") {
    
    
    let url = URL(string: "https://source.unsplash.com/random")!
    
    func getImage() -> AnyPublisher<UIImage?, Never> {
        URLSession.shared
            .dataTaskPublisher(for: url)
            .map { data, _ in UIImage(data: data) }
            .print("image")
            .replaceError(with: nil)
            .eraseToAnyPublisher()
    }
    
    let taps = PassthroughSubject<Void, Never>()
    
    taps
        .map { _ in getImage() }
        .switchToLatest()
        .sink(receiveValue: { _ in })
        .store(in: &subscriptions)
    
    taps.send()  // 랜덤사진 받아오는 URL 호출
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
        taps.send() // 3초후 URL 호출
    }
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 3.1) {
        taps.send() // 3.1초후 URL 호출 (이전 URL 호출 취소)
    }
}

/*
 ——— Example of: switchToLatest - Network Request ———
 image: receive subscription: (DataTaskPublisher)
 image: request unlimited
 image: receive value: (Optional(<UIImage:0x6000009dc630 anonymous {1080, 720} renderingMode=automatic(original)>))
 image: receive finished
 image: receive subscription: (DataTaskPublisher)
 image: request unlimited
 image: receive cancel  <--- 여기서 취소
 image: receive subscription: (DataTaskPublisher)
 image: request unlimited
 image: receive value: (Optional(<UIImage:0x6000009d2fd0 anonymous {1080, 1350} renderingMode=automatic(original)>))
 image: receive finished
 */


+추가

unsplash 의 랜덤사진을 가져오는 URL이기 때문에 아래 빨간표시를 클릭해서 사진 확인도 할 수 있습니다.



사진에 우클릭 후 Value History를 누르면 이전값도 확인할 수 있습니다.

 

 

merge(with:)

다른 publisher를 합치는 연산자 입니다.

merge(with:)


var subscriptions = Set<AnyCancellable>()

example(of: "merge(with:)") {
    let publisher1 = PassthroughSubject<Int, Never>()
    let publisher2 = PassthroughSubject<Int, Never>()
    
    publisher1
        .merge(with: publisher2)
        .sink(receiveCompletion: { _ in print("Completed")},
              receiveValue: { print($0) })
        .store(in: &subscriptions)
    
    publisher1.send(1)
    publisher1.send(2)
    
    publisher2.send(3)
    
    publisher1.send(4)
    publisher2.send(5)
    
    publisher1.send(completion: .finished)
    publisher2.send(completion: .finished)
}

/*
 ——— Example of: merge(with:) ———
 1
 2
 3
 4
 5
 Completed
 */

 

combineLatest

publisher들의 최신 값들을 합쳐 튜플값으로 방출합니다.
*아래 예시 코드보단 그림이 좀더 이해가 잘갑니다.

combineLatest

var subscriptions = Set<AnyCancellable>()

example(of: "combineLatest") {
    
    let publisher1 = PassthroughSubject<Int, Never>()
    let publisher2 = PassthroughSubject<String, Never>()
    
    publisher1
        .combineLatest(publisher2)
        .sink(receiveCompletion: { _ in print("Completed")},
              receiveValue: { print("P1: \($0), P2: \($1)") })
        .store(in: &subscriptions)
    
    publisher1.send(1) // 생략
    publisher1.send(2) 
    
    publisher2.send("a") // P1: 2 와 합쳐짐
    publisher2.send("b") // P1: 2 와 합쳐짐, P1: 3과 합쳐짐
    
    publisher1.send(3) 
    
    publisher2.send("c") // P1: 3 과 합쳐짐 
    
    publisher1.send(completion: .finished)
    publisher2.send(completion: .finished)
}

/*
 ——— Example of: combineLatest ———
 P1: 2, P2: a
 P1: 2, P2: b
 P1: 3, P2: b
 P1: 3, P2: c
 Completed
 */

 

 

zip

두개의 publisher에서 방출되는 값들을 짝지어 하나로 합쳐주는 역할을 합니다.
* 각 이벤트의 요소들 갯수가 일치하는만큼만 묶어 내려줍니다.

zip
var subscriptions = Set<AnyCancellable>()

example(of: "zip") {
    
    let publisher1 = PassthroughSubject<Int, Never>()
    let publisher2 = PassthroughSubject<String, Never>()
    
    publisher1
        .zip(publisher2)
        .sink(
            receiveCompletion: { _ in print("Completed") },
            receiveValue: { print("P1: \($0), P2: \($1)") }
        )
        .store(in: &subscriptions)
    
    publisher1.send(1)
    publisher1.send(2)
    publisher2.send("a") // (1, "a")
    publisher2.send("b") // (2, "b")
    publisher1.send(3)
    publisher2.send("c") // (3, "c")
    publisher2.send("d") // 짝이 없으므로 Pass ~
    
    publisher1.send(completion: .finished)
    publisher2.send(completion: .finished)
    
}

/*
 ——— Example of: zip ———
 P1: 1, P2: a
 P1: 2, P2: b
 P1: 3, P2: c
 Completed
 */​

 

728x90