[2026] Swift 에러 처리 | do-catch, throw, Result

[2026] Swift 에러 처리 | do-catch, throw, Result

이 글의 핵심

enum NetworkError: Error { case badURL case timeout case noConnection case serverError(Int) }.

들어가며

throws·try·docatch실패 가능한 연산을 시그니처에 드러냅니다. Result 타입으로 값과 오류를 한꺼번에 다루는 패턴도 많습니다.

1. 에러 정의

Error 프로토콜

아래 코드는 swift를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

enum NetworkError: Error {
    case badURL
    case timeout
    case noConnection
    case serverError(Int)
}
enum ValidationError: Error {
    case emptyField(String)
    case invalidFormat(String)
    case outOfRange(String, min: Int, max: Int)
}

LocalizedError

아래 코드는 swift를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

enum FileError: LocalizedError {
    case notFound
    case permissionDenied
    
    var errorDescription: String? {
        switch self {
        case .notFound:
            return "파일을 찾을 수 없습니다"
        case .permissionDenied:
            return "권한이 없습니다"
        }
    }
}

2. throw와 try

기본 에러 처리

다음은 swift를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

func fetchData(from url: String) throws -> String {
    guard !url.isEmpty else {
        throw NetworkError.badURL
    }
    
    // 네트워크 요청 시뮬레이션
    if url.contains("timeout") {
        throw NetworkError.timeout
    }
    
    return "데이터"
}
// do-catch로 처리
do {
    let data = try fetchData(from: "https://example.com")
    print("성공: \(data)")
} catch NetworkError.badURL {
    print("잘못된 URL")
} catch NetworkError.timeout {
    print("타임아웃")
} catch {
    print("기타 에러: \(error)")
}

try? - Optional 변환

아래 코드는 swift를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 에러 무시, nil 반환
let data = try? fetchData(from: "https://example.com")
if let data = data {
    print("데이터: \(data)")
} else {
    print("실패")
}
// 기본값 제공
let result = try? fetchData(from: "bad") ?? "기본값"

try! - 강제 언래핑

// 에러 발생 시 크래시 (확실할 때만 사용)
let data = try! fetchData(from: "https://example.com")

3. Result<Success, Failure>

Result 사용

다음은 swift를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

func fetchData(from url: String) -> Result<String, NetworkError> {
    guard !url.isEmpty else {
        return .failure(.badURL)
    }
    
    if url.contains("timeout") {
        return .failure(.timeout)
    }
    
    return .success("데이터")
}
// 패턴 매칭
switch fetchData(from: "https://example.com") {
case .success(let data):
    print("성공: \(data)")
case .failure(let error):
    print("실패: \(error)")
}
// map, flatMap
let result = fetchData(from: "https://example.com")
    .map { $0.uppercased() }
    .mapError { _ in NetworkError.noConnection }

Result와 throws 변환

아래 코드는 swift를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Result → throws
func getData() throws -> String {
    let result = fetchData(from: "https://example.com")
    return try result.get()
}
// throws → Result
func getDataResult() -> Result<String, Error> {
    Result { try fetchData(from: "https://example.com") }
}

4. 실전 예제

예제: 사용자 검증

다음은 swift를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

enum ValidationError: LocalizedError {
    case emptyName
    case invalidAge
    case invalidEmail
    
    var errorDescription: String? {
        switch self {
        case .emptyName:
            return "이름은 필수입니다"
        case .invalidAge:
            return "나이는 0~150 사이여야 합니다"
        case .invalidEmail:
            return "유효하지 않은 이메일"
        }
    }
}
struct User {
    let name: String
    let age: Int
    let email: String
    
    static func validate(name: String, age: Int, email: String) throws -> User {
        guard !name.isEmpty else {
            throw ValidationError.emptyName
        }
        
        guard age >= 0 && age <= 150 else {
            throw ValidationError.invalidAge
        }
        
        guard email.contains("@") else {
            throw ValidationError.invalidEmail
        }
        
        return User(name: name, age: age, email: email)
    }
}
// 사용
do {
    let user = try User.validate(name: "", age: 25, email: "test@example.com")
    print("사용자 생성: \(user.name)")
} catch {
    print("검증 실패: \(error.localizedDescription)")
}

실전 심화 보강

실전 예제: Result로 네트워크 레이어 경계 정리

throws만 쓰는 대신 Result를 반환하면 비동기 체이닝과 테스트가 단순해질 때가 많습니다. 다음은 swift를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import Foundation
enum NetworkFailure: Error {
    case invalidURL
    case status(Int)
    case decoding
}
struct HTTP {
    static func getString(_ urlString: String) async -> Result<String, NetworkFailure> {
        guard let url = URL(string: urlString) else {
            return .failure(.invalidURL)
        }
        do {
            let (data, resp) = try await URLSession.shared.data(from: url)
            if let http = resp as? HTTPURLResponse, !(200...299).contains(http.statusCode) {
                return .failure(.status(http.statusCode))
            }
            guard let text = String(data: data, encoding: .utf8) else {
                return .failure(.decoding)
            }
            return .success(text)
        } catch {
            return .failure(.decoding)
        }
    }
}

async throws와의 선택은 호출부 스타일과 팀 컨벤션에 따릅니다.

자주 하는 실수

  • try!로 네트워크 응답을 강제 언래핑해 크래시를 내는 경우.
  • LocalizedError 없이 사용자에게 내부 enum 케이스 이름이 그대로 노출되는 경우.
  • Resultmap/flatMap 실패 타입이 달라져 타입 추론이 꼬이는 경우.

주의사항

  • @frozen이 아닌 라이브러리 에러 타입은 미래 케이스 추가에 대한 switch 경고가 생길 수 있습니다(@unknown default).

실무에서는 이렇게

  • 도메인 에러와 전송 계층 에러를 분리하고, UI에는 매핑된 메시지만 노출합니다.
  • 로깅은 NSError userInfo 또는 OSLog에 상관 ID를 남깁니다.

비교 및 대안

API장점
throws언어 관용, 짧은 호출부
Result값으로 합성, 동기 코드와 유사
async + throwsSwift 동시성과 자연스럽게 결합

추가 리소스


정리

핵심 요약

  1. Error 프로토콜: 에러 타입 정의
  2. throw: 에러 발생
  3. do-catch: 에러 처리
  4. try/try?/try!: 에러 전파 방식
  5. Result: 성공/실패를 값으로 표현

다음 단계


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3