에러 핸들링과 Toast Message 활용하기

챗봇을 개발하면서 예외, 오류 처리 등 이용자 경험에 필수적으로 포함되어야 하는 부분에 대해 구현하기 위해 일단 발생할 수 있는 상황을 정리해 에러 핸들링을 나열하고 이 핸들링을 통해 특정 상황에 맞는 Toast 메세지를 날려주는 로직을 구현하려고 한다!

 

먼저 최대한 기능별 모듈화를 통해 프로젝트를 쪼개 구현하고 있기 때문에 에러 핸들러 패키지를 만들어서 사용하는 방식을 사용해보자!

이 글에서는 대표적인 사례로 인터넷 연결이 끊겼을때 상황을 가정해 로직 구현을 해보려고 한다.

public enum NetworkError: Error {
    case networkError(code: Int, underlying: Error?)
    case noInternetConnection
    case yesInternetConnection
    case timeout
    case invalidResponse
    case unknown(underlying: Error?)

    public var localizedDescription: String {
        switch self {
        case .networkError(let code, _):
            return "네트워크 오류가 발생했습니다. 오류 코드: \\(code)"
        case .noInternetConnection:
            return "인터넷 연결이 없습니다."
        case .yesInternetConnection:
            return "인터넷 연결되었습니다."
        case .timeout:
            return "네트워크 요청 시간이 초과되었습니다."
        case .invalidResponse:
            return "잘못된 응답을 받았습니다."
        case .unknown:
            return "알 수 없는 네트워크 오류가 발생했습니다."
        }
    }
}

각 상황 별로 네트워크 연결 관련 상황을 바탕으로 enum을 통해 해당 내용을 리스트업한 다음 해당 내용을 가져가 토스트 메세지에 localizedDescription을 각 상황에 맞게 넣어줄 수 있도록 하려고 한다.

 

그 다음 토스트 메세지를 호출하는 로직을 프로토콜을 활용해 추상화 한다.

public protocol ToastNotifying {
    @MainActor func showToast(message: String)
}

MainActor를 활용해 UI 업데이트와 관련 된 해당 로직이 안정적으로 메인 스레드에서 실행됨을 보장할 수 있도록 했다.

public func showToast(message: String) {
    withAnimation(.spring(response: 0.3)) {
        toastMessage = message
    }

    DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) { [weak self] in
        withAnimation(.easeOut(duration: 0.2)) {
            self?.toastMessage = nil
        }
    }
}

그 다음 Toast메세지가 등장한 다음 2.5 초 뒤 사라질 수 있도록 해당 로직을 넣어주면 토스트 메세지에 대한 로직 구현이 쉽게 완료된다.

 

검색해보니 앱을 사용하면서 네트워크가 연결되어있는지 여부를 추적할 수 있는 방법은 NWPathMonitor를 활용해 연결 상태 변화를 감지하는 방법이 있는 것 같아 이 방법으로 한번 적용해보려고 한다.

 

공식문서를 확인해보니 NWPathMonitor 내 pathUpdateHandler를 활용하면 현재 단말기의 네트워크 상태 값을 전달 받을 수 있다고 한다!

 

 

이를 바탕으로 상태에 맞게 메세지를 전달해보자

private let monitor: NWPathMonitor
private let queue = DispatchQueue(label: "NetworkMonitorQueue")

@Published public var isConnected: Bool = true

private init() {
    monitor = NWPathMonitor()
    monitor.pathUpdateHandler = { [weak self] path in
        let newStatus = (path.status == .satisfied)
        Task { @MainActor [weak self] in
            guard let self = self else { return }
            if self.isConnected != newStatus {
                self.isConnected = newStatus
                if newStatus {
                    await ErrorHandler.shared.toastNotifier.showToast(message: NetworkError.yesInternetConnection.localizedDescription)
                } else {
                    await ErrorHandler.shared.toastNotifier.showToast(message: NetworkError.noInternetConnection.localizedDescription)
                }
            }
        }
    }
}

public func startMonitoring() {
    monitor.start(queue: queue)
}

public func stopMonitoring() {
    monitor.cancel()
}

별도의 Queue를 활용해 네트워크 모니터링 업무를 수행하도록 맞춰주게 되면 현재 상태를 전달받으면서 각 상황에 맞는 메세지를 넣어주면 이렇게 각각 상황에 맞는 메세지를 넣을 수 있게 된다.

 

이 내용을 바탕으로 앱단으로 넘어가 앱 델리게이트 내

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        Task { @MainActor in
            NetworkMonitor.shared.startMonitoring()
        }
        
        return true
    }
}

네트워크 모니터링을 시작할 수 있도록 전역으로 선언해주면 이제 앱에서 네트워크 상태를 감지하고 해당 값을 통해 토스트 메세지가 정상적으로 뜰 수 있게 된다. ( 원하는 뷰에 선언만 해주면 됨 )

 

 

토스트 메세지를 통해 해당 앱의 오류 상태를 이용자에게 전달할 수 있게 됐지만 일단 할 수 있도록만 구현해보았고 조금 더 효율적으로 구현할 수 있는 방법에 대해서 확인해보고 적용해 내용을 추가해야겠다!