[Swift 공식문서 읽기]Error Handling

llim🧚🏻‍♀️·2021년 8월 24일
0

Swift

목록 보기
17/26
post-thumbnail

안녕하세요. 엘림입니다🙇🏻‍♀️

Swift 공식 문서를 정독하기 시리즈입니다!

제 스타일대로 정리했으니 추가적으로 더 필요한 정보는
공식문서 링크를 눌러 확인해주세용!

좀 더 편하게 보기위해 한국어로 번역된 사이트를 함께 확인했습니다!ㅎㅎ

자, 그럼 시작해볼까요

이 글은 공부하면서 작성한 글이기 때문에 잘못된 정보가 있을 수 있습니다.🥺
금방 잊어버릴... 미래의 저에게 다시 알려주기 위한 글이다보니
혹시라도 틀린 부분이 있다면, 댓글로 친절하게 알려주시길 부탁드립니다.🙏


에러 처리

프로그램 실행시 에러가 발생하면, 그 상황에 대한 적절한 처리가 필요합니다. 이 과정을 에러 처리라고 부릅니다. Swift에서는 런타임에 발생한 에러를 처리하기 위해 발생, 감지, 증식, 조작을 지원하는 일급 클래스를 제공합니다.

어떤 명령은 항상 완전히 실행되는 것이 보장되지 않는 경우가 있습니다. 그런 경우에 옵셔널을 사용해 에러를 발생시켜 값이 없다는 것을 표시할 수는 있지만, 어떤 종류의 에러가 발생했는지 확인할 수는 없습니다. 이럴 때는 구체적으로 발생한 에러를 확인할 수 있어야, 코드를 작성하는 사람이 각 에러의 경우에 따라 적절한 처리를 할 수 있습니다.

Swift에서의 에러 처리는 Cocoa의 NSError 클래스와 상호 호환되는 여러 핸들링 패턴을 사용합니다.

에러의 표시와 발생

Swift에서 에러는 Error 프로토콜을 따르는 타입의 값으로 표현됩니다. 비어있는(Empty) 이 프로토콜은, 프로토콜을 따르는 타입이 에러 처리를 위해 사용될 수 있다는 것을 가리킵니다.

Swift의 열거형은 특별히 관련된 에러를 그룹화하고, 추가적인 정보를 제공하기에 적합합니다.

enum VendingMachineError: Error {
	case invalidSeletion
    case insufficientFunds(coinsNeeded: Int)
    case outOfStock
}

throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

에러를 발생시킴으로써 무언가 기대하지 않았던 동작이 발생했고 작업을 계속 수행할 수 없다는 것을 알려줄 수 있습니다. 에러를 발생시키기 위해서는 throw 구문을 사용할 수 있습니다.
(에러를 반환하는 throw 구문은 일반적인 반환 구문인 return 구문과 비슷한 성능을 보여줍니다.)

에러 처리

에러가 발생하면 특정 코드 영역이 해당 에러를 처리하도록 해야합니다. 예를 들어 문제를 해결하거나, 혹은 우회할 수 있는 방법을 시도합니다. 또는 사용자에게 실패 상황을 알리는 것도 예외 처리의 한 방법이 될 수 있습니다.

Swift에서 4가제 에러를 처리하는 방법이 있습니다.
1. 에러가 발생한 함수에서 리턴값으로 에러를 반환해, 해당 함수를 호출한 코드에서 에러를 처리하는 방법
2. do-catch 구문을 사용하는 방법
3. 옵셔널 값을 반환하는 방법
4. assert를 사용하여 강제로 크래쉬를 발생시키는 방법

에러를 발생시키는 함수 사용하기

어떤 함수나 메소드 혹은 이니셜라이저가 에러를 발생시킬 수 있다는 것을 알리기 위해 throws 키워드를 함수 선언부의 파라미터 뒤(리턴 값이 있다면 -> 키워드 앞)에 붙일 수 있습니다. 이러한 함수를 throwing function이라고 부릅니다.
오직 throwing function만이 에러를 발생시킬 수 있습니다. 만약 throwing function이 아닌 함수에서 throw가 발생한다면 반드시 그 함수내에서 throw에 대해 처리돼야 합니다.

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)")
    }
}

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)
}

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

Do-Catch를 이용해 에러를 처리하기

에러가 do 구문 안에서 발생한다면, 발생한 에러의 종류를 catch 구문으로 구분해 처리할 수 있습니다.

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."

에러를 발생시킬 수 있는 함수는 try 표현 안에서 호출되고, 에러가 발생하자마자 catch 구문에 전달해 적절한 처리를 할 수 있게 합니다.(에러가 발새하지 않는다면, do 구문이 실행됩니다. 만약 실패하면, 그 즉시 catch로 가기 때문에 try보다 아래 적혀있는 코드는 실행되지 않습니다.)
만약 발생한 에러 종류에 해당하는 catch 구문이 없다면, 마지막 catch 구문에 걸리게 되어서 지역 에러 상수인 error로 처리할 수 있습니다.

하지만 발생 되는 에러는 반드시 관련된 특정 코드 영역에서 처리돼야 합니다. 에러를 발생 시키지 않는 함수에서는 관련된 do-catch 구문에서 그 에러를 반드시 처리해야 하고, 에러를 발생 시키는 함수에서는 에러를 do-catch 구문에서 처리하거나 함수를 호출한 곳에서 반드시 에러를 처리해야 합니다. 만약 에러가 발생한 곳에서 에러에 대해 아무런 처리도 하지 않으면 런타임 에러가 발생하게 됩니다.

에러를 옵셔널 값으로 변환하기

try? 구문을 사용해 에러를 옵셔널 값으로 변환할 수 있습니다. 만약 에러가 try? 표현 내에서 발생한다면, 그 표현의 값은 nil이 됩니다.

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()의 타입이 어떤 것이든 상관없이 옵셔널이 됩니다. 이 함수에서는 integer를 리턴하기 때문에 x와 y는 옵셔널 integer입니다. try?는 만약 발생하는 모든 에러를 같은 방법으로 처리하고 싶을 때 사용합니다.

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

에러 발생을 중지하기

함수나 메소드에서 에러가 발생되지 않을 것이라고 확신하는 경우 try!를 사용할 수 있습니다. 혹은 runtime assertion을 사용해 에러가 발생하지 않도록 할 수 있습니다. 하지만 만약 에러가 발생하면 런타임 에러가 발생하게 됩니다. 예를 들어, 다음 코드는 loadImage(atPath:) 함수를 사용해 주어진 경로에서 이미지 리소스를 불러오거나 이미지를 불러오는데 실패한 경우 에러를 발생 시킵니다. 이 경우에는 앱이 배포될때 이미지가 포함되 배포되기 때문에 런타임에는 아무 에러도 발생되지 않을 것이라 확신할 수 있어 try!를 사용하는 것이 적절합니다.
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")

정리 액션 기술(defer)

defer 구문을 이용해 함수가 종료 된 후 파일 스트림을 닫거나, 사용했던 자원을 해지 하는 등의 일을 할 수 있습니다. defer가 여러개가 있는 경우 가장 마지막 줄부터 실행 됩니다. 즉 bottom-up 순으로 실행 됩니다.

func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file) // block이 끝나기 직전에 실행, 주로 자원 해제나 정지에 사용
        }
        while let line = try file.readline() {
            // Work with the file.
        }
        // close(file) is called here, at the end of the scope.
    }
}

오늘도 스위프트 공식문서를 정리해보았군욥~
다음편도 힘내보겠습니다!

감사합니다🙇🏻‍♀️

profile
한달 차 iOS 개발자입니다🐥

0개의 댓글