Swift 오류처리 : 자판기 예제로 배우는 throw, throws, try, do-catch, switch-case, defer
🧯 Swift 오류 처리
Swift에서 프로그래밍을 하다 보면 예상치 못한 문제가 생길 수 있다. 예를 들어 자판기를 만든다고 할 때, 사용자가 잘못된 금액을 넣거나 제품이 부족한 상황 등이 오류가 될 수 있다. 이런 문제들을 깔끔하게 처리하는 것이 바로 오류 처리(Error Handling)이고, Swift는 이를 위한 다양한 기능을 제공한다. 오늘은 그 개념들을 정말 쉽게, 단계별로 정리해보았다(:
1. 오류는 어떻게 표현할까?
Swift에서는 Error라는 빈 프로토콜을 통해 오류를 정의한다. 보통은 enum을 사용해서 오류의 종류를 나눈다. 열거형은 다양한 오류 상태를 구분하기에 적합한 구조다.
enum VendingMachineError: Error {
case invalidInput
case insufficientFunds(moneyNeeded: Int)
case outOfStock
}
각 case는 자판기에서 발생할 수 있는 오류 상황이다. 예를 들어 돈을 잘못 넣었을 때는 invalidInput, 돈이 부족하면 insufficientFunds, 재고가 없으면 outOfStock이다.
2. 오류를 던지기: throw
throw는 "지금 문제가 생겼어요!" 하고 Swift에게 알려주는 신호다. 오류를 던질 때는 위에서 만든 enum case를 사용한다.
throw VendingMachineError.invalidInput
하지만 throw는 아무 함수에서나 쓸 수 없다. 이 오류를 던지려면 해당 함수가 오류를 던질 수 있다고 미리 선언해야 한다. 그 선언이 바로 throws다.
3. 오류를 던질 수 있게 하기: throws
throws는 함수 선언부에 붙는다. 이 함수 안에서는 오류를 던질 수 있다는 걸 알려주는 역할을 한다.
func receiveMoney(_ money: Int) throws {
guard money > 0 else {
throw VendingMachineError.invalidInput
}
}
여기서 throw는 오류를 실제로 발생시키는 실행 구문이고, throws는 이 함수가 그런 행동을 할 수 있다는 약속이다.
정리하면:
- throws는 throw를 쓸 수 있는 울타리
- throw는 실제로 오류를 던지는 실행
4. 자판기 예제로 익히는 전체 흐름
class VendingMachine {
let itemPrice: Int = 100
var itemCount: Int = 5
var deposited: Int = 0
func receiveMoney(_ money: Int) throws {
guard money > 0 else {
throw VendingMachineError.invalidInput
}
deposited += money
print("\(money)원 받음")
}
func vend(numberOfItems: Int) throws -> String {
guard numberOfItems > 0 else {
throw VendingMachineError.invalidInput
}
guard numberOfItems * itemPrice <= deposited else {
throw VendingMachineError.insufficientFunds(
moneyNeeded: numberOfItems * itemPrice - deposited
)
}
guard itemCount >= numberOfItems else {
throw VendingMachineError.outOfStock
}
deposited -= numberOfItems * itemPrice
itemCount -= numberOfItems
return "\(numberOfItems)개 제공함"
}
}
5. 오류를 처리하는 방법: do-try-catch
throws 함수는 호출할 때 try를 붙여야 한다. 그리고 오류가 날 수 있기 때문에 do-catch 블록으로 감싸서 대응해야 한다.
do {
try machine.receiveMoney(0)
} catch VendingMachineError.invalidInput {
print("입력이 잘못되었습니다")
} catch VendingMachineError.insufficientFunds(let moneyNeeded) {
print("\(moneyNeeded)원이 부족합니다")
} catch VendingMachineError.outOfStock {
print("수량이 부족합니다")
}
switch로 오류 분기 처리하기
위 코드처럼 catch 여러 개를 써도 되지만, 하나의 catch로 받아서 내부에서 switch로 나누는 방법도 있다.
do {
try machine.receiveMoney(0)
} catch {
switch error {
case VendingMachineError.invalidInput:
print("입력이 잘못되었습니다")
case VendingMachineError.insufficientFunds(let moneyNeeded):
print("\(moneyNeeded)원이 부족합니다")
case VendingMachineError.outOfStock:
print("수량이 부족합니다")
default:
print("알 수 없는 오류: \(error)")
}
}
왜 switch로도 처리할까?
- catch가 하나라서 코드가 덜 길다
- 외부에서는 Error 타입으로 받고, 내부에서 세부 분류하기 때문에 유연하다
- 때로는 enum 이외의 다양한 오류 타입을 묶어 처리할 수 있다
💡 switch-case는 꼭 enum일 때만 쓰는 건 아니다.
Swift의 switch는 어떤 값이든 조건에 따라 나눌 수 있게 해주는 분기문이다.
위 예시에서는 error 값이 어떤 오류인지 case로 나눠서 처리한 것이다.
6. try?, try! 도 있다
Swift는 편하게 쓰기 위한 try?, try! 도 제공한다.
try?
- 오류가 발생하면 nil 반환
- 성공하면 Optional 값 반환
let result = try? machine.vend(numberOfItems: 2)
print(result) // Optional("2개 제공함") 또는 nil
try!
- "절대 오류 안 날 거야!"라는 확신이 있을 때 사용
- 오류 나면 앱이 바로 죽는다 (크래시 발생)
let result = try! machine.vend(numberOfItems: 1)
print(result) // "1개 제공함"
7. 보너스 개념: defer
defer는 "지금 당장은 아니지만, 이 함수 끝나기 전에 무조건 실행해줘!" 라고 예약해두는 기능이다.
예를 들어 문을 열었다가 함수 끝나기 전에 반드시 닫아야 한다면 이렇게 쓸 수 있다:
func useResource() {
print("문 열기")
defer {
print("문 닫기")
}
print("안에서 작업 중...")
}
// 출력 결과:
// 문 열기
// 안에서 작업 중...
// 문 닫기
- defer는 주로 정리 작업, 파일 닫기, 리소스 해제 등에 사용된다
- defer는 함수가 어떻게 종료되든 간에 무조건 실행된다는 특징이 있다
8. 핵심 정리 요약
enum + Error | 오류 타입 정의 |
throw | 실제 오류 발생 시 사용 |
throws | 오류를 던질 수 있는 함수로 선언 |
try | 오류 발생 가능한 함수 호출 시 사용 |
do-catch | 오류를 분기해서 처리 |
switch-case | 오류를 하나의 catch에서 나눠서 처리 가능 |
try? | 오류 시 nil 반환 |
try! | 오류 없다고 확신할 때 (주의!) |
defer | 함수 끝나기 전에 반드시 실행되는 코드 예약 |
throw, throws, try, do-catch의 관계를 이해하고, 자판기처럼 현실적인 예제로 연습해보자. switch-case, defer 등도 실무에서 자주 쓰이니 함께 알아두면 좋다. 🥰 무엇보다 중요한 것은 직접 여러 예제를 작성하며 손에 익히는 것!
'IT' 카테고리의 다른 글
Swift 핵심 개념 정리 : 제네릭, 프로토콜, ARC와 강한 순환 참조 (0) | 2025.05.23 |
---|---|
Swift 고차 함수 쉽게 배우기: map, filter, reduce 실전 예제 (0) | 2025.05.22 |
Swift 익스텐션 활용법: 연산 프로퍼티, 메서드, 서브스크립트 추가하기 (0) | 2025.05.14 |
스위프트 프로토콜 vs 클래스 상속: 유연한 기능 구현을 위한 최적의 선택 (0) | 2025.05.11 |
Swift : assert, guard, 그리고 제어 흐름 제어문 (0) | 2025.05.11 |
댓글
이 글 공유하기
다른 글
-
Swift 핵심 개념 정리 : 제네릭, 프로토콜, ARC와 강한 순환 참조
Swift 핵심 개념 정리 : 제네릭, 프로토콜, ARC와 강한 순환 참조
2025.05.23 -
Swift 고차 함수 쉽게 배우기: map, filter, reduce 실전 예제
Swift 고차 함수 쉽게 배우기: map, filter, reduce 실전 예제
2025.05.22 -
Swift 익스텐션 활용법: 연산 프로퍼티, 메서드, 서브스크립트 추가하기
Swift 익스텐션 활용법: 연산 프로퍼티, 메서드, 서브스크립트 추가하기
2025.05.14 -
스위프트 프로토콜 vs 클래스 상속: 유연한 기능 구현을 위한 최적의 선택
스위프트 프로토콜 vs 클래스 상속: 유연한 기능 구현을 위한 최적의 선택
2025.05.11