티스토리 뷰

iOS

[Combine] Chapter10 : Debugging

Peppo 2023. 1. 3. 22:43
728x90

Printing events

 

print(_:to:)

디버깅을 할때 print(_:to:) 메서드로 chain메서드 중간중간 상황을 파악할 수 있습니다.

import Combine
import Foundation


let subscription = (1...3).publisher
    .print("publisher")
    .sink { _ in }

// 1    
//publisher: receive subscription: (1...3)
// 2
//publisher: request unlimited
// 3
//publisher: receive value: (1)
//publisher: receive value: (2)
//publisher: receive value: (3)
// 4
//publisher: receive finished


아래를 출력합니다.

1. 구독을 받고 업스트림 publisher의 내용을 보여줄 때.
2. subscriber의 수요 요청
3. publisher에서 방출되는 모든 값들
4. 완료 이벤트 (completion event)

TextOutputStream

TextOutputStream 프로토콜을 사용하여 print를 커스텀할 수도 있습니다.

import Combine
import Foundation

class TimeLogger: TextOutputStream {
    private var previous = Date()
    private let formatter = NumberFormatter()

    init() {
        formatter.maximumFractionDigits = 5
        formatter.minimumFractionDigits = 5
    }

    func write(_ string: String) {
        let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty else { return }
        let now = Date()
        print("+\(formatter.string(for: now.timeIntervalSince(previous))!)s: \(string)")
        previous = now
    }
}

let subscription = (1...3).publisher
    .print("publisher", to: TimeLogger())
    .sink { _ in }
    
//+0.00244s: publisher: receive subscription: (1...3)
//+0.00852s: publisher: request unlimited
//+0.00016s: publisher: receive value: (1)
//+0.00006s: publisher: receive value: (2)
//+0.00006s: publisher: receive value: (3)
//+0.00005s: publisher: receive finished

 

 

 

handleEvents(receiveSubscription:receiveOutput:receiveCompletion:receiveCancel:receiveRequest:)

만약 네트워크 통신 도중 문제가 발생할 경우 sink로는 확인이 어렵습니다. 
아래와 같이 코드를 적어보고 print를 해보세요.
import Combine
import Foundation

let request = URLSession.shared
    .dataTaskPublisher(for: URL(string: "https://www.raywenderlich.com/")!)

request
    .sink(receiveCompletion: { completion in
        print("Sink received compeltion: \(completion)")
    }) { (data, _) in
        print("Sink received data: \(data)")
    }
    
// 무반응


아무것도 print되지 않는걸 볼 수 있는데, handleEvent() 메서드를 사용해서 print되는걸 보겠습니다.

- request(publisher)와 sink 사이에 handleEvent()메서드를 넣어줍니다.

import Combine
import Foundation

let request = URLSession.shared
    .dataTaskPublisher(for: URL(string: "https://www.raywenderlich.com/")!)

request
    // 추가
    .handleEvents(receiveSubscription: { _ in
        print("Network request will start")
    }, receiveOutput: { _ in
        print("Network request data received")
    }, receiveCancel: {
        print("Network request cancelled")
    })
    .sink(receiveCompletion: { completion in
        print("Sink received compeltion: \(completion)")
    }) { (data, _) in
        print("Sink received data: \(data)")
    }

// Network request will start
// Network request cancelled


결과 값을 보면 알 수 있듯, 
네트워크 요청을 시작하지만 바로 취소가 됩니다.

이유는 Cancellable을 저장(store) 하지 않으면, 코드 범위를 벗어 날때 deallocated (할당해제) 및 취소 되기 때문입니다.

아래와 같이 수정해주면 

import Combine
import Foundation

let request = URLSession.shared
    .dataTaskPublisher(for: URL(string: "https://www.raywenderlich.com/")!)

private var cancellables = Set<AnyCancellable>()  // 추가

request
    .handleEvents(
        receiveSubscription: { _ in
        print("Network request will start")},
        receiveOutput: { _ in
        print("Network request data received")},
        receiveCancel: {
        print("Network request cancelled")})
    .sink(receiveCompletion: { completion in
        print("Sink received compeltion: \(completion)")
    }) { (data, _) in
        print("Sink received data: \(data)")}
    .store(in: &cancellables) // 추가
    
// Network request will start
// Network request data received
// Sink received data: 264701 bytes
// Sink received compeltion: finished


정상적으로 print되는걸 볼 수 있습니다.

 

breakpoint(

들어가기전
breakpoint 메서드는 playground에서 적용이 되지 않습니다. 

위의 방법으로 디버깅을 했는데도 문제를 찾기 어렵다면,
breakpoint() 메서드를 사용합니다.
upstream publisher에서 오류가 발생하면 break 되며, 
오류가 나는곳을 찾을 수 있습니다.

예시 코드는 아래와 같습니다.

upstream publisher가 정수 값 11 ~ 14는 절대 발생하지 않는경우를 예로들어, 이 경우에만 중단점을 구성하고 확인할 수 있습니다. 또한 조건부로 구독 및 완료 시간을 중단할 수 있지만, handleEvents 연산자와 같은 취소를 가로챌 수는 없습니다.
.breakpoint(receiveOutput: { value in
  return value > 10 && value < 15
})​


728x90