에러 처리
- 에러처리는 프로그램내에서 에러 상황이 발생하는 경우 복구하고 이에 대응하는 프로세스이다. Swift는 실행타임에 throw, catch, propagate, recover 지원하는 firtst-class 지원한다.
- 일부 연산은 항상 정상적으로 실행되고 결과를 내지 않을 수 있다. Optional은 값이 없을 수 있다는 것을 나타내지만 연산이 실패하는 경우 실패하는 원인을 알고 이에 적절히 응답하는 것이 중요하다.
- 예제로 디스크에 있는 파일에서 데이터를 읽고 처리하는 태스크를 예로 들어보자. 이 태스크가 실패하는 다양한 이유가 있을 수 있다. 지정한 경로에 파일이 없을 수도 있고 파일에 읽기 권한이 없거나 호환 가능한 포맷으로 인코딩이 안되어 있을 수도 있다. 이런 여러 상황을 구분하면 프로그램에서 일부 에러를 해결할 수도 있고 해결하지 못하는 에러에 대해서는 사용자와 인터렉션이 가능하다.
- 주의 : 에러처리는 기존 NSError 클래스를 사용하는 패턴을 따른다.
에러 표현 및 Throw
- Swift에서 에러는 ErrorType 프로토콜에 만족하는 타입의 값으로 표현된다. 이 빈 프로토콜은 타입이 에러 처리에 사용할 수 있다는 것을 뜻한다.
- Swift에서 열거형은 관련 에러 상태를 그룹으로 묶어서 처리하는데 적합하다.
enum VendingMachineError: ErrorType {
case InvalidSelection
case InsufficientFunds(coinsNeeded: Int)
case OutOfStock
}
- 에러를 throw하는 것은 의도하지 않은 일이 발생해서 정상적인 제어 절차를 수행할 수 없다는 것을 뜻한다. 에러를 throw하기 위해서 thow 절을 사용한다. 예를 들어 에러를 thow하는 다음 코드는 밴딩머신에서 5개 추가 동전이 필요하다는 것을 나타낸다.
throw VendingMachineError.InsufficientFunds(coinsNeeded: 5)
에러처리
- 에러를 throw하면 코드에서 반드시 에러를 처리하는 책잉이 있다. 예를 들면 문제를 바로 잡거나 선택적인 접근 혹은 사용자에게 실패 알리기 등이다.
- Swift에서 에러를 처리하는 4가지 방식이 있다.
- propagate : 에러가 발생한 함수를 호출한 함수로 에러를 전달하기
- do-catch 절을 사용해서 에러 처리
- optional 값으로 에러처리
- 에러가 발생하지 않도록 assert하기
- 함수가 에러를 throw하면 프로그램의 진행 흐름이 바뀌게 된다. 에러를 throw하는 코드를 빠르게 찾아내는 것이 중요하다. 코드에서 이 위치를 알면, 에러를 throw할 수 있는 함수나 메소드 혹은 초기화 코드 이전에 try 키워드(try? 혹은 try!)를 넣는다.
- 주의 : Swift에서 에러 처리는 다른 언어에서 예외 처리와 비슷하다. 다른 언어에서 제공하는 예외처리와 다른점은 콜백을 unwind하지 않는다는 것이다. 이는 프로세스가 처리할 일이 발생하기 때문이다. throw를 사용하는 경우 성능은 return을 사용하는 경우와 맞먹는다.
Throwing 함수를 이용한 Erorr 전파
- 함수, 메소드, 초기화가 에러를 throw한다는 것을 나타내기 위해서 함수 선언부에서 파라미터 뒤에 throws 키워드를 사용한다.
func canThrowErrors() throws -> String
func canThrowErrors() -> String
throwing 함수는 함수 내부에서 발생한 throw된 에러를 전파시킨다.
주의 : throwing 함수만 에러를 전파시킬 수 있다. throwing 함수가 아닌 함수 내부에서 에러를 throw되면 함수 내부에서 처리해야만 한다.
아래 예제에서 VendingMachine 클래스는 vend 메소드는 요청한 아이템이 유효하지 않으면(재고가 없거나 현재 보유한 돈을 초과하는 비용이 드는 경우) 적절한 VendingMachineError을 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 dispenseSnack(snack: String) {
print("Dispensing \(snack)")
}
func vend(itemNamed name: String) throws {
{
guard var item = inventory[name]
else {
throw VendingMachineError.InvalidSelection
}
}
guard item.count > 0 else {
throw VendingmachineError.OutofStock
}
guard item.price
- vend 메소드 구현부를 보면 메소드를 일찍 빠져나가고 적절한 에러를 throw하기 위해서 guard 문을 사용했다. throw 문은 즉시 프로그램 제어를 전환되기 때문에, 요구사항을 모두 만족하는 것만 처리된다.
- vend 메소드는 throw하는 어떤 에러든 전파시키기 때문에, 이를 호출하는 코드내에 위치에서 바로 에러를 처리하던가(do-catch문, try?나 try!) 아니면 계속 전파시키는 것을 해야한다. 아래 예에서 throwing 함수와 다른 에러는 buyFavoriteSnack 함수가 호출되는 지점에서 위로 전파될 것이다.
let favoriteSnacks = [
"Alice": "Chips",
"Bob": "Licorice",
"Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throw {
let snackName = favoriteSnacks[person]??"Cnady Bar"
let vendingMachine.vend(itemNamed: snackName)
}
- 이 예제에서 buyFavoriteSnack 함수는 인자로 받은 사람이 좋아하는 스낵을 찾고 vend 메소드를 호출해서 이것을 구매하는 동작을 한다. vend 메소드는 에러를 throw하기 때문에, 앞부분에 try 키워드를 붙여서 호출한다.
Do-Catch를 사용한 에러처리
- 에러를 처리하는 코드블록에 do-catch 문을 사용한다.
- do를 사용한 부분에서 에러가 발생하면 catch 부분에서 처리할 수 있는 에러 중에 하나인지를 결정한다.
- 아래 일반적인 do-catch 문 사용 방법이다.
- ```swfit do { try
} catch 에러패턴1{ //에러패턴1 처리
} catch 에러패턴2 where 조건 { //에러패턴2 처리 }
* catch 뒤에 어떤 에러인지를 표시해서 해당 에러를 처리한다는 것을 보여줄 수 있다. 만약 catch에서 패턴을 가지지 않는 경우 해당 에러를 error라는 지역 상수명으로 연결시켜서 에러로 처리할 수 있게 한다.
* catch 문에서는 do에서 발생한 모든 에러를 처리할 필요는 없다. 만약 어떤 catch 문도 발생한 에러를 처리하지 않는다면 에러를 범위내에서 전파시키게 된다. 여기서 범위는 에러를 처리하는 do-catch 부분이거나 throwing 함수 내부가 될 수 있다. 다음 코드에서는 VendingMachineError 열거형이 가지는 3가지 경우를 모두 처리한다. 하지만 이외 다른 에러는 범위내에서 처리되어야만 한다.
```swift
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
try buyFavoriteSnack("Alice", vendingMachine: vendingMachine)
} catch VendingMachineError.InvalidSelection {
print("Invalid Selection")
} catch VendingMachineError.OutOfStock {
print("Out of Stock.")
} catch VendingMachineError.InsufficientFunds() {
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
}
- 위 예제에서 buyFavoriteSnack 함수는 try와 함께 사용했다. 왜냐하면 에러를 throw하기 때문이다. 만약 에러가 throw되면 즉시 실행이 catch 문으로 이동하게 된다. 여기서 계속실행할 것인지 아니면 전파시킬 것인지를 결정한다. 에러가 발생하지 않으면 많은 do 내부에서 순차적으로 실행된다.
에러를 Optional 값으로 변환하기
- try?를 사용하면 optional 값으로 바꿔서 에러를 처리할 수 있다. 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 함수가 반환하는 어떤 타입에 대해서 optional이다. 여기서 함수는 정수를 반환하므로 x와 y는 optional 정수다.
- try? 사용하면 에러처리 코드를 간결하게 작성할 수 있다. 예제로 다음 코드는 데이터를 가져오는데 실패하면 nil을 반환하는 코드다.
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() {
return data
}
if let data = try? fetchDataFromServer() {
return data
}
return nil
}
에러전파 막기
- 어떤 때는 thrwoing 함수나 메소드가 실제로 런타임에 에러를 throw 하지 않는다는 것을 알고 있다. 이런 경우 try!를 쓰면 에러가 전파되는 것을 막고 어떤 에러도 throw 되지 않도록 런타임 assertion에서 호출을 감쌀 수 있다. 만약 에러가 실제로 throw되면, 우리는 런타인 에러를 갖게 된다.
- 예로 당므 코드는 loadImage 함수를 사용하는데 이것은 주어진 경로로 이미지 리소스를 로드하거나 이미지가 로드 되지 않으면 에러를 발생시킨다. 이 경우 이미지는 어플리케이션에서 탑재되므로 어떤 에러도 런타임에서 throw되지는 않을 것이다. 따라서 에러 전파를 막는게 적절하다.
let photo = tyr! loadImage("./Resources/John Appleaseed.jpg")
클린업 동작 지시하기
- 현재 블록의 코드 실행을 마치고 빠져나가기 전에 수행해야할 부분이 있다면
defer문을 사용할 수 있다. 이 부분에서 클린업과 관련된 동작을 넣으면 된다. 현재 코드 블록에서 정상적으로 빠져나가는지 아니면 에러에 의해서 빠져나가는지 상관없이 해당 코드 블록을 빠져나갈 때 처리된다. 자주 사용하는 경우가 파일을 닫거나 할당한 메모리를 해제하는 처리를 주로 수행한다. * defer문은 현재 코드를 벗어날때 까지 실행을 미룬다. 사용법은 defer 키워드 이후에 실제로 빠져나가기 전에 수행할 코드 블록을 작성한다. defer를 사용한 부분에서는 제어 흐름을 바꾸는 break나 return을 사용하지 않는다. 또 에러를 throw하지 않도록 한다. 만약 defer가 여러 개 사용되었다면 스택과 같이 먼저 defer를 사용한 것부터 실행된다. 맨 처음에 defer 사용한 것이 가장 나중에 실행되게 된다.
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline(){
//파일과 동작
}
//close(file)이 여기서 호출될 것이다. 여기가 빠져나가기 직전이기 때문이다.
}
}
- 위에 예제에서 defer문을 사용해서 open 함수에 대응하는 close 함수가 마지막에 호출되도록 했다.
- 주의 : 에러처리 코드가 없이도 defer 문을 사용할 수 있다.
정의
사용용도
문법
예제
Quiz
- 정의에 대한 질문
- 용도에 대한 질문
- 예제 관련