이 포스팅은 Swift 시리즈 20 편 중 11 번째 글 입니다.

  • Part 1 - 01: Optional, Any, AnyObject, nil
  • Part 2 - 02: struct, class, enum
  • Part 3 - 03: Closure
  • Part 4 - 04: Property
  • Part 5 - 05: 상속, 생성자, 소멸자
  • Part 6 - 06: 옵셔널 체이닝과 nil 병합
  • Part 7 - 07: 타입 캐스팅
  • Part 8 - 08: assert, guard
  • Part 9 - 09: Protocol
  • Part 10 - 10: Extention
  • Part 11 - This Post
  • Part 12 - 12: 고차함수
  • Part 13 - 13: ARC(Automatic Reference Counting)
  • Part 14 - 14: Access control, Access modifier
  • Part 15 - 15: Generics
  • Part 16 - 16: Optional에 대한 깊은 이해
  • Part 17 - 17: Lazy Variables
  • Part 18 - 18: Enumeration
  • Part 19 - 19: Initialization
  • Part 20 - 20: Concurrency
▼ 목록 보기

오류 처리

  • 스위프트에서 오류는 Error라는 프로토콜을 준수하는 타입을 통해 표현된다.
  • 보통 열거형을 사용한다.

표현

enum VendingMachineError: Error {
    case invalidInput
    case insuffcientFunds(moneyNeeded: Int)
    case outOfStock
}
  • 자판기의 동작 오류를 표현했다.
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
        }
        self.deposited += money
        print("\(money)원 받음")
    }
    
    // 물건을 파는 메서드
    func vend(numberOfItems numberOfItemsToVend: Int) throws -> String {
        // 원하는 아이템의 수량이 잘못 입력되었으면 오류를 던진다.
        guard numberOfItemsToVend > 0 else {
            throw VendingMachineError.invalidInput
        }
        
        // 현재까지 넣은 돈이 구매하려는 물건의 개수 대비 금액에 비해 적으면 에러를 낸다.
        guard numberOfItemsToVend * itemPrice <= deposited else {
            let moneyNeeded: Int
            moneyNeeded = numberOfItemsToVend * itemPrice - deposited
            
            throw VendingMachineError.insuffcientFunds(moneyNeeded: moneyNeeded)
        }
        
        // 구매하려는 수량보다 비치되어 있는 아이템이 적으면 에러를 낸다.
        guard itemCount >= numberOfItemsToVend else {
            throw VendingMachineError.outOfStock
        }
        
        // 오류가 없으면 정상처리를 한다.
        let totalPrice = numberOfItemsToVend * itemPrice
        self.deposited -= totalPrice
        self.itemCount -= numberOfItemsToVend
        
        return "\(numberOfItemsToVend)개 제공함"
    }
}
  • 오류를 던질 가능성이 있는 메서드의 경우 throws를 사용하여 오류 내포 함수임을 나타낸다.

오류 처리

  • 저렇게 작성한 코드에 대해 어떻게 처리할지에 대한 코드도 작성해야 한다.
  • 오류에 따라 다른 처리방법이라던지, 다른 시도를 한다던지, 사용자에게 오류를 알리고 선택을 하도록 한다던지등의 코드를 작성해야 함
  • throws가 달려있는 함수는 try를 사용하여 호출한다.

do-catch

let machine: VendingMachine = VendingMachine()

do {
    try machine.receiveMoney(0)
} catch VendingMachineError.invalidInput {
    print("입력이 잘못되었습니다.")
} catch VendingMachineError.insuffcientFunds(let moneyNeeded) {
    print("\(moneyNeeded)원이 부족합니다.")
} catch VendingMachineError.outOfStock {
    print("수량이 부족합니다.")
} // 입력이 잘못되었습니다.
  • 가장 정석적인 방법으로 모든 오류 케이스에 대응된다.
// catch를 계속해서 쓰는 것이 귀찮다면
do {
    try machine.receiveMoney(300)
} catch /* (let error) */ { // 넘어오는 에러의 이름을 바꿔줄 수 있다. 기본은 error
    switch error {
    case VendingMachineError.invalidInput:
        print("입력이 잘못되었습니다.")
    case VendingMachineError.insuffcientFunds(let moneyNeeded):
        print("\(moneyNeeded)원이 부족합니다.")
    case VendingMachineError.outOfStock:
        print("수량이 부족합니다.")
    default:
        print("알수 없는 오류 \(error)")
    }
} // 300원 받음

  • catch를 계속해서 쓰는 것이 귀찮다면 이렇게도 할 수 있다.
// 굳이 에러를 따로 처리할 필요가 없다면
var result: String?
do {
    result = try machine.vend(numberOfItems: 4)
} catch {
    print(error)
}
  • 에러를 개별로 처리할 필요가 없다면 이와 같이 써도 무방하다.

try?

  • 별도의 오류 처리 결과를 통보받지 않고 오류가 발생했을 경우 결과값을 nil로 받을 수 있다.
  • 즉 오류가 발생하면 nil, 발생하지 않으면 옵셔널 타입으로 리턴
let machine: VendingMachine = VendingMachine()
var result: String?

result = try? machine.vend(numberOfItems: 2)
result // Optional("2개 제공함")

result = try? machine.vend(numberOfItems: 2)
result // nil

try!

  • 오류가 발생하지 않을 것이라는 확신을 가질 때 사용
  • 바로 값을 리턴 받을 수 있지만
  • 런타임 오류가 발생
result = try! machine.vend(numberOfItems: 1)
result // 1개 제공함

//result = try! machine.vend(numberOfItems: 1)
// 런타임 오류 발생!

Reference