Error Handling

Groot·2022년 8월 24일
0

Swift Language Guide

목록 보기
16/24
post-thumbnail

Error Handling

  • 오류 처리는 프로그램의 오류 조건에 응답하고 복구하는 프로세스입니다.
  • Swift는 런타임에 복구 가능한 오류를 throw, catch, 전파 및 조작하기 위한 최고 수준의 지원을 제공합니다.
  • 일부 작업은 항상 실행을 완료하거나 유용한 출력을 생성한다고 보장되지 않습니다.
  • 옵셔널은 값이 없음을 나타내는 데 사용되지만 작업이 실패할 때 코드가 그에 따라 응답할 수 있도록 실패 원인을 이해하는 것이 종종 유용합니다.
  • 예를 들어 디스크의 파일에서 데이터를 읽고 처리하는 작업을 고려하십시오.
  • 파일이 지정된 경로에 없거나, 파일에 읽기 권한이 없거나, 파일이 호환 가능한 형식으로 인코딩되지 않는 등 이 작업이 실패할 수 있는 여러 가지 방법이 있습니다.
  • 이러한 다양한 상황을 구별하면 프로그램이 일부 오류를 해결하고 해결할 수 없는 오류를 사용자에게 전달할 수 있습니다.

    Swift의 오류 처리는 Cocoa 및 Objective-C의 NSError 클래스를 사용하는 오류 처리 패턴과 상호 운용됩니다.
    이 클래스에 대한 자세한 내용은 Swift에서 코코아 오류 처리를 참조하세요.

📌 Representing and Throwing Errors

  • Swift에서 오류는 오류 프로토콜을 준수하는 유형의 값으로 표시됩니다.
  • 이 빈 프로토콜은 유형을 오류 처리에 사용할 수 있음을 나타냅니다.
  • Swift 열거형은 관련 오류 조건 그룹을 모델링하는 데 특히 적합하며 관련 값을 사용하여 오류의 특성에 대한 추가 정보를 전달할 수 있습니다.
  • 예를 들어 게임 내에서 자판기를 작동하는 오류 조건을 나타내는 방법은 다음과 같습니다.
    enum VendingMachineError: Error {
        case invalidSelection
        case insufficientFunds(coinsNeeded: Int)
        case outOfStock
    }
  • 오류를 던지면 예상치 못한 일이 발생하여 정상적인 실행 흐름을 계속할 수 없음을 나타낼 수 있습니다.
  • 오류를 발생시키기 위해 throw 문을 사용합니다.
  • 예를 들어 다음 코드는 자판기에 5개의 추가 동전이 필요함을 나타내는 오류를 발생시킵니다.
    throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

📌 Handling Errors

  • 오류가 발생하면 주변의 일부 코드가 오류 처리를 담당해야 합니다.
  • 예를 들어 문제를 수정하거나, 다른 접근 방식을 시도하거나, 사용자에게 오류를 알리는 등의 작업을 수행해야 합니다.
  • Swift에서 오류를 처리하는 네 가지 방법이 있습니다.
  • 함수에서 해당 함수를 호출하는 코드로 오류를 전파하거나, do-catch 문을 사용하여 오류를 처리하거나, 오류를 선택적 값으로 처리하거나, 오류가 발생하지 않을 것이라고 주장할 수 있습니다.
  • 각 접근 방식은 아래 섹션에 설명되어 있습니다.
  • 함수에서 오류가 발생하면 프로그램의 흐름이 변경되므로 코드에서 오류가 발생할 수 있는 위치를 빠르게 식별할 수 있어야 합니다.
  • 코드에서 이러한 위치를 식별하려면 오류를 발생시킬 수 있는 함수, 메서드 또는 이니셜라이저를 호출하는 코드 조각 앞에 try 키워드를 작성합니다. (try?, try!)

    Swift의 오류 처리는 try, catch 및 throw 키워드를 사용하는 다른 언어의 예외 처리와 유사합니다.
    Objective-C를 포함한 많은 언어의 예외 처리와 달리 Swift의 오류 처리에는 계산 비용이 많이 들 수 있는 프로세스인 호출 스택 해제가 포함되지 않습니다.
    따라서 throw 문의 성능 특성은 return 문의 성능 특성과 비슷합니다.

📍 Propagating Errors Using Throwing Functions

  • 함수, 메서드 또는 초기화 프로그램에서 오류가 발생할 수 있음을 나타내려면 함수 선언에서 매개변수 뒤에 throw 키워드를 작성합니다.

  • throws로 표시된 함수를 throwing 함수라고 합니다.

  • 함수가 반환 유형을 지정하는 경우 반환 화살표(->) 앞에 throw 키워드를 작성합니다.

    func canThrowErrors() throws -> String
    
    func cannotThrowErrors() -> String
  • throwing 함수는 내부에서 throw된 오류를 호출된 범위로 전파합니다.

    throw하는 함수만 오류를 전파할 수 있습니다. 던지지 않는 함수 내에서 발생한 모든 오류는 함수 내에서 처리되어야 합니다.

  • 아래 예제에서 VendingMachine 클래스에는 요청된 항목을 사용할 수 없거나, 재고가 없거나, 현재 예치된 금액을 초과하는 비용이 있는 경우 적절한 VendingMachineError를 발생시키는 vend(itemNamed:) 메서드가 있습니다.

    struct Item {
        var price: Int
        var count: Int
    }
    
    class VendingMachine {
        var inventory = [
            "Candy Bar": Item(price: 12, count: 7),
            "Chips": Item(price: 10, count: 4),
            "Pretzels": Item(price: 7, count: 11)
        ]
        var coinsDeposited = 0
    
        func vend(itemNamed name: String) throws {
            guard let item = inventory[name] else {
                throw VendingMachineError.invalidSelection
            }
    
            guard item.count > 0 else {
                throw VendingMachineError.outOfStock
            }
    
            guard item.price <= coinsDeposited else {
                throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
            }
    
            coinsDeposited -= item.price
    
            var newItem = item
            newItem.count -= 1
            inventory[name] = newItem
    
            print("Dispensing \(name)")
        }
    }
  • vend(itemNamed:) 메서드의 구현은 guard 문을 사용하여 메서드를 일찍 종료하고 스낵 구매에 대한 요구 사항이 충족되지 않는 경우 적절한 오류를 발생시킵니다.

  • throw 문은 즉시 프로그램 제어를 전송하므로 이러한 모든 요구 사항이 충족되는 경우에만 항목이 판매됩니다.

  • vend(itemNamed:) 메서드는 발생하는 모든 오류를 전파하기 때문에 이 메서드를 호출하는 모든 코드는 do-catch 문, try? 또는 try!를 사용하여 오류를 처리하거나 계속 전파해야 합니다.

  • 예를 들어 아래 예제의 buyFavoriteSnack(person:vendingMachine:) 도 던지는 함수입니다.

  • vend(itemNamed:) 메서드에서 발생하는 모든 오류는 buyFavoriteSnack(person:vendingMachine:) 함수가 호출되는 지점까지 전파됩니다.

    let favoriteSnacks = [
        "Alice": "Chips",
        "Bob": "Licorice",
        "Eve": "Pretzels",
    ]
    func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
        let snackName = favoriteSnacks[person] ?? "Candy Bar"
        try vendingMachine.vend(itemNamed: snackName)
    }
  • 이 예제에서 buyFavoriteSnack(person: vendingMachine:) 함수는 주어진 사람이 가장 좋아하는 간식을 찾고 vend(itemNamed:) 메서드를 호출하여 구매를 시도합니다.

  • throwing initializer는 throwing 함수와 같은 방식으로 오류를 전파할 수 있습니다.

  • 예를 들어, 아래 목록의 PurchasedSnack 구조에 대한 이니셜라이저는 초기화 프로세스의 일부로 throwing 함수를 호출하고 발생하는 오류를 호출자에게 전파하여 처리합니다.

    struct PurchasedSnack {
        let name: String
        init(name: String, vendingMachine: VendingMachine) throws {
            try vendingMachine.vend(itemNamed: name)
            self.name = name
        }
    }

📍 Handling Errors Using Do-Catch

  • do-catch 문을 사용하여 코드 블록을 실행하여 오류를 처리합니다.

  • do 절의 코드에서 오류가 발생하면 catch 절과 일치하여 그 중 어느 것이 오류를 처리할 수 있는지 결정합니다.

  • 다음은 do-catch 문의 일반적인 형식입니다.

    do {
        try expression
        statements
    } catch pattern 1 {
        statements
    } catch pattern 2 where condition {
        statements
    } catch pattern 3, pattern 4 where condition {
        statements
    } catch {
        statements
    }
  • catch 다음에 패턴을 작성하여 해당 절이 처리할 수 있는 오류를 나타냅니다.

  • catch 절에 패턴이 없으면 이 절은 모든 오류와 일치하고 오류를 error라는 로컬 상수에 바인딩합니다.

  • 예를 들어 다음 코드는 VendingMachineError 열거의 세 가지 경우 모두와 일치합니다.

    var vendingMachine = VendingMachine()
    vendingMachine.coinsDeposited = 8
    do {
        try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
        print("Success! Yum.")
    } catch VendingMachineError.invalidSelection {
        print("Invalid Selection.")
    } catch VendingMachineError.outOfStock {
        print("Out of Stock.")
    } catch VendingMachineError.insufficientFunds(let coinsNeeded) {
        print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
    } catch {
        print("Unexpected error: \(error).")
    }
    // Prints "Insufficient funds. Please insert an additional 2 coins."
  • 위의 예에서 buyFavoriteSnack(person:vendingMachine:) 함수는 오류를 던질 수 있기 때문에 try 표현식에서 호출됩니다.

  • 오류가 발생하면 실행이 즉시 전파를 계속 허용할지 여부를 결정하는 catch 절로 전송됩니다.

  • 일치하는 패턴이 없으면 오류는 최종 catch 절에 의해 catch되고 로컬 오류 상수에 바인딩됩니다.

  • 오류가 발생하지 않으면 do 문의 나머지 문이 실행됩니다.

  • catch 절은 do 절의 코드가 던질 수 있는 모든 가능한 오류를 처리할 필요가 없습니다.

  • catch 절이 오류를 처리하지 않으면 오류가 주변 범위로 전파됩니다.

  • 그러나 전파된 오류는 일부 주변 범위에서 처리해야 합니다.

  • 던지지 않는 함수에서 바깥쪽 do-catch 문은 오류를 처리해야 합니다.

  • 던지는 함수에서 do-catch 문을 둘러싸거나 호출자가 오류를 처리해야 합니다.

  • 오류가 처리되지 않고 최상위 범위로 전파되면 런타임 오류가 발생합니다.

  • 예를 들어 위의 예제는 VendingMachineError가 아닌 모든 오류가 대신 호출 함수에 의해 catch되도록 작성할 수 있습니다.

    func nourish(with item: String) throws {
        do {
            try vendingMachine.vend(itemNamed: item)
        } catch is VendingMachineError {
            print("Couldn't buy that from the vending machine.")
        }
    }
    
    do {
        try nourish(with: "Beet-Flavored Chips")
    } catch {
        print("Unexpected non-vending-machine-related error: \(error)")
    }
    // Prints "Couldn't buy that from the vending machine."
  • Nutrition(with:) 함수에서 vend(itemNamed:)가 VendingMachineError 열거의 경우 중 하나인 오류를 발생시키면 Nutrition(with:)은 메시지를 출력하여 오류를 처리합니다.

  • 그렇지 않으면, Nutrition(with:)이 오류를 호출 사이트로 전파합니다. 그런 다음 일반 catch 절에 의해 오류가 포착됩니다.

  • 여러 관련 오류를 포착하는 또 다른 방법은 catch 뒤에 쉼표로 구분하여 나열하는 것입니다.

func eat(item: String) throws {
    do {
        try vendingMachine.vend(itemNamed: item)
    } catch VendingMachineError.invalidSelection, VendingMachineError.insufficientFunds, VendingMachineError.outOfStock {
        print("Invalid selection, out of stock, or not enough money.")
    }
}
  • Eat(item:) 함수는 포착할 자판기 오류를 나열하고 해당 오류 텍스트는 해당 목록의 항목에 해당합니다.
  • 나열된 세 가지 오류 중 하나가 발생하면 이 catch 절이 메시지를 인쇄하여 처리합니다.
  • 나중에 추가될 수 있는 자동 판매기 오류를 포함하여 다른 모든 오류는 주변 범위로 전파됩니다.

📍 Converting Errors to Optional Values

  • 당신은 try?를 사용해 선택적 값으로 변환하여 오류를 처리합니다.

  • try를 평가하는 동안 오류가 발생하면 ? 표현식에서 표현식의 값은 nil입니다.

  • 예를 들어 다음 코드에서 x와 y는 동일한 값과 동작을 갖습니다.

    func someThrowingFunction() throws -> Int {
        // ...
    }
    
    let x = try? someThrowingFunction()
    
    let y: Int?
    do {
        y = try someThrowingFunction()
    } catch {
        y = nil
    }
  • someThrowingFunction()에서 오류가 발생하면 x와 y의 값은 nil입니다.

  • 그렇지 않으면 x 및 y 값은 함수가 반환한 값입니다.

  • x와 y는 someThrowingFunction()이 반환하는 모든 유형의 선택 사항입니다.

  • 여기서 함수는 정수를 반환하므로 x와 y는 선택적 정수입니다.

  • try?를 사용해 모든 오류를 동일한 방식으로 처리하려는 경우 간결한 오류 처리 코드를 작성할 수 있습니다.

  • 예를 들어 다음 코드는 데이터를 가져오기 위해 여러 접근 방식을 사용하거나 모든 접근 방식이 실패하면 nil을 반환합니다.

    func fetchData() -> Data? {
        if let data = try? fetchDataFromDisk() { return data }
        if let data = try? fetchDataFromServer() { return data }
        return nil
    }

📍 Disabling Error Propagation

  • 때때로 던지는 함수나 메서드가 실제로 런타임에 오류를 던지지 않는다는 것을 알고 있습니다.
  • 오류 전파를 비활성화하고 오류가 발생하지 않는다는 런타임 어설션으로 호출을 래핑하는 표현식 앞에 try!를 쓸 수 있습니다
  • 실제로 오류가 발생하면 런타임 오류가 발생합니다.
  • 예를 들어 다음 코드는 주어진 경로에서 이미지 리소스를 로드하거나 이미지를 로드할 수 없는 경우 오류를 발생시키는 loadImage(atPath:) 함수를 사용합니다.
  • 이 경우 이미지가 애플리케이션과 함께 제공되기 때문에 런타임에 오류가 발생하지 않으므로 오류 전파를 비활성화하는 것이 적절합니다.
    let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")

📌 Specifying Cleanup Actions

  • 코드 실행이 현재 코드 블록을 떠나기 직전에 명령문 세트를 실행하기 위해 defer 문을 사용합니다.
  • 이 명령문을 사용하면 실행이 현재 코드 블록을 떠나는 방법에 관계없이 수행해야 하는 필요한 정리를 수행할 수 있습니다.
  • 예를 들어 defer 문을 사용하여 파일 설명자가 닫히고 수동으로 할당된 메모리가 해제되도록 할 수 있습니다.
  • defer 문은 현재 범위가 종료될 때까지 실행을 연기합니다.
  • 이 문장은 defer 키워드와 나중에 실행될 문장으로 구성된다.
  • 지연된 문에는 break 또는 return 문과 같이 명령문 밖으로 제어를 이전하거나 오류를 발생시키는 코드가 포함될 수 없습니다.
  • 지연된 작업은 소스 코드에 작성된 순서의 역순으로 실행됩니다.
  • 즉, 첫 번째 defer 문의 코드가 마지막으로 실행되고 두 번째 defer 문의 코드가 두 번째에서 마지막으로 실행되는 식입니다.
  • 소스 코드 순서의 마지막 defer 문이 먼저 실행됩니다.
    func processFile(filename: String) throws {
        if exists(filename) {
            let file = open(filename)
            defer {
                close(file)
            }
            while let line = try file.readline() {
                // Work with the file.
            }
            // close(file) is called here, at the end of the scope.
        }
    }
  • 위의 예에서는 defer 문을 사용하여 open(:) 함수에 해당하는 close(:) 호출이 있는지 확인합니다.

    오류 처리 코드가 포함되지 않은 경우에도 defer 문을 사용할 수 있습니다.

profile
I Am Groot

0개의 댓글