---
id: ios-swift-result-type
title: Swift Result 타입과 throws — 에러 모델 선택
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [ios, swift, error-handling, result, vibe-coding]
tech_stack: { language: "Swift 5+", applicable_to: ["iOS", "macOS"] }
applied_in: []
aliases: [Result<Success, Failure>, throws, do/catch]
---

# Swift Result 타입과 throws

> Swift 는 두 에러 모델 — `throws`(검사 안 됨, 호출자가 do/catch) + `Result<S, F>`(값으로 전달). 두 가지 다 알아야 한다. 보통 비동기 콜백 → Result, async/await → throws.

## 📖 핵심 개념
- `throws`: throw 가능 함수. 호출 시 `try` 강제. `Error` 프로토콜 (구체 타입 알 수 없음).
- `Result<S, F: Error>`: 결과를 값으로. F 는 구체 타입 — 패턴 매칭 가능.
- async/await 는 throws 와 페어 (Result 대신).

## 💻 코드 패턴

### throws + async (모던)
```swift
enum LoadError: Error {
    case notFound
    case network(URLError)
    case decode(DecodingError)
}

func loadUser(id: String) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    do {
        let (data, response) = try await URLSession.shared.data(from: url)
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw LoadError.notFound }
        return try JSONDecoder().decode(User.self, from: data)
    } catch let e as URLError { throw LoadError.network(e) }
      catch let e as DecodingError { throw LoadError.decode(e) }
}

// 호출
do { let user = try await loadUser(id: "1") }
catch LoadError.notFound { showNotFoundScreen() }
catch LoadError.network(let urlErr) { showRetry(reason: urlErr.localizedDescription) }
catch { showGenericError() }
```

### Result — 콜백 기반 또는 명시적 값
```swift
func loadUser(id: String, completion: @escaping (Result<User, LoadError>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, response, error in
        if let error = error { completion(.failure(.network(URLError(.badURL)))); return }
        // ...
        completion(.success(user))
    }.resume()
}

loadUser(id: "1") { result in
    switch result {
    case .success(let user): self.user = user
    case .failure(.notFound): showNotFound()
    case .failure(.network(let e)): showRetry(...)
    case .failure(.decode): logBug()
    }
}
```

### Result 와 async 변환
```swift
let result = await Task { try await loadUser(id: "1") }.result
switch result {
case .success(let user): ...
case .failure(let error): ...
}
```

## 🤔 의사결정 기준
| 상황 | 권장 |
|---|---|
| async 함수 | `throws` + 구체 enum Error |
| 콜백 기반 (legacy) | `Result<S, F>` |
| 컴퓨터 사이언스적 의미 (값으로 다루기) | Result |
| 도메인 에러 + 명시적 case 처리 강제 | typed throws (Swift 6+) 또는 Result |
| 단순 fail-fast (재시도 없음) | throws |

## ❌ 안티패턴
- **`Error` 만 throw + 호출자가 추측**: 어떤 에러 가능한지 모름. 구체 enum.
- **catch all → fatalError**: production 사용자 crash. 의미 있는 fallback.
- **try? 로 nil 변환만**: 어떤 에러였는지 손실. 디버깅 어려움.
- **throws 함수에서 do/catch 내부 throw 변환 누락**: 외부엔 raw URLError 노출. 도메인 에러로 변환.
- **Result 와 throws 혼용**: 일관성 깨짐. 한 도메인은 한 모델.
- **Result.failure 후 .success 반환 (swap)**: 잘못된 의미. case 명확.
- **async 함수에서 Result 반환**: 중복. throws 가 자연스러움.

## 🤖 LLM 활용 힌트
- 도메인 에러는 enum 으로 정의 + LocalizedError 채택.
- async 함수는 throws, callback 은 Result.

## 🔗 관련 문서
- [[Error_Handling_Result_vs_Throw]]
- [[iOS_Swift_Concurrency_async_await]]
