검색결과 리스트
2015/07/10에 해당되는 글 1건
- 2015.07.10 [swift2] Swift에서 에러 처리 (Error Handling)
글
[swift2] Swift에서 에러 처리 (Error Handling)
(본 포스트는 The Swift Programming Language - Swift 2 Prerelease. 의 Error Handling 장을 번역한 문서입니다.)
에러처리는 프로그램에서 에러조건에 대응하고 회복하는 과정입니다. Swift는 런타임에 회복 가능한 에러를 던지고, 잡고,
전파하고, 조작하는 first-class(1급) support를 제공합니다. 몇몇 함수와 메소드는 항상 완전한 실행과 유용한 정보를
출력한다고 보장할 수 없습니다. Optional은 값의 부재를 표현하기 위해 사용되지만 함수가 실패했을때 실패원인을 이해하고
거기에 맞춰 코드를 대응하는게 더 유용할때가 있습니다.
디스크의 파일로부터 데이터를 읽고 처리하는 작업을 예로 들어보겠습니다.
경로에 파일이 없거나 읽기 권한이 없거나, 파일이 호환되지 않는 포맷으로 인코딩 되어있는 것 등 여러가지 이유로 작업이
실패할 수 있습니다. 이러한 상황의 차이를 구별하고 필요하다면 사용자와 커뮤니케이션해야 에러를 해결하고 회복할수 있습니다.
1. 에러의 표현 (Representing Errors)
스위프트에서 에러는 ErrorType 프로토콜을 구현하는 타입값으로 표현됩니다.
Swift enumerations는 특히 관련있는 에러조건(에러의 원인에 대한 정보를 연관값으로 가진채) 그룹을 모델링하는데
적합합니다. 이러한 이유로, Swift enumerations는 ErrorType을 자동으로 구현하고, 컴파일러에 의해 생성된 구현체를 갖습니다.
자판기의 에러조건을 표현하기 위한 예입니다.
enum VendingMachineError: ErrorType {
case InvalidSelection
case InsufficientFunds(required: Double)
case OutOfStock
}
이 예제에서 자판기는 다음과 같은 이유로 실패할수 있습니다.
- 1. 요청한 아이템이 유효한 선택이 아닌 경우. InvalidSelection 으로 표현.
- 2. 요청한 아이템 비용이 입금한 비용보다 더 큰 경우. InsufficentFunds로 표현.
연관된 Double 값은 트랜잭션을 완료하기 위해 필요한 추가요금. - 3. 요청한 아이템의 재고가 없는 경우. OutOfStock으로 표현.
2. 에러 던지기 (Throwing Errors)
함수 또는 메서드가 에러를 던질수 있다는것을 나타내기 위해, 선언부의 파라미터 다음에 throws 키워드를 사용합니다.
만일 return 타입이 있으면, throws 키워드는 리턴 화살표 앞에 표기합니다. 함수, 메소드, 클로져는 명시적으로 표시하지
않으면 에러를 던질 수 없습니다.
func canThrowErrors() throws -> String
func canThrowErrors() -> String
throws를 선언한 함수의 몸체 어느 시점에서도 throw 문을 사용하여 에러를 던질수 있습니다.
아래 예제에서 vend(itemNamed:) 함수는 요청한 아이템의 재고가 없거나, 금액이 부족할 때 에러를 던집니다.
stuct Item {
var price:Double
var count:Int
}
var inventory = [
"Candy Bar": Item(price:1.25, count:7),
"Chips": Item(price: 1.00, count:4),
"Pretzels": Item(price: 0.75, count:11)
]
var amountDeposited = 1.00
func vend(itemNamed name:String) throws {
guard var item = inventory[name] else {
throw VendingMachineError.InvalidSelection
}
guard item.count > 0 else {
throw VendingMachineError.OutOfStock
}
if amountDeposited >= item.price {
amountDeposited -= item.price
--item.count
inventory[name] = item
} else {
let amountRequired = item.price - amountDeposited
throw VendingMachineError.InsufficentFunds(required: amountRequired)
}
}
먼저 quard 문은 item 상수와 count 변수를 inventory의 대응하는 값과 바인드하기 위해 사용되었습니다.
item 이 inventory에 없으면 InvalidSelection 에러를 던집니다. 그 다음으로 요청한 item의 count를 체크해
가용성을 결정합니다. count가 0보다 같거나 작으면 OutOfStock 에러를 던집니다. 마지막으로 요청한 아이템의 가격과
현재 입금된 금액을 비교합니다. 입금된 금액이 아이템의 가격보다 크면, 입금액에서 가격만큼 차감되고, inventory에 있는
재고량이 감소합니다. 그리고 함수는 요청한 아이템을 반환합니다. 그렇지 않으면 초과된 금액이 연관값으로 전달되어 InsufficentFunds 에러를 던져집니다. throw 문은 즉시 프로그램의 제어를 이동시키기 때문에 아이템은 구매를 위한
모든 요구조건이 만족 될때만 판매됩니다.
throwing 함수를 호출할 때, 호출부 앞에 try를 붙힐 수 있습니다. 이 키워드는 함수가 에러를 던질수 있고,
try 이후 코드라인들이 실행되지 않을 수 있음을 암시합니다.
let favoriteSnacks = [
"Alice": "Chips",
"Bob": "Licorice",
"Eve": "Pretzels"
]
func buyFavoriteSnack(person:String) throws {
// a ?? b 구문은 a가 nil이 아니면 a를, nil이면 b를 반환.
let snackName = favoriteSnacks[person] ?? "Candy Bar"
try vend(itemNamed: snackName)
}
buyFavoriteSnack() 함수는 person이 가장 좋아하는 snack을 찾고, 그것을 구매하려고 시도합니다.
좋아하는 snack이 리스트에 없다면 candy bar를 사려고 시도합니다. 에러를 던질수도 있는 vend 함수가 호출되기
때문에 try 를 함수 앞에 사용했습니다. buyFavoriteSnack(_:) 함수는 throwing 함수이기 때문에, vend 함수
던지는 어떤 에러도 buyFavoriteSnack 함수가 호출되는 상위로 전파될수 있습니다..
3. 에러를 잡고 처리하기 (Catching and Handling Errors)
에러를 잡아 처리하기 위해 do-catch 문을 사용할 수 있습니다.
do {
try function that throws
...
} catch pattern {
statements
}
에러가 던져지면 catch 절에 의해 처리 될때까지 에러는 외부 스코프로 전파됩니다. catch 절은 catch 키워드 다음에
매칭할 에러패턴 그리고 실행할 구문집합으로 구성됩니다. switch 문처럼 컴파일러는 catch절이 모두 포괄되어 있는지
검사합니다. 그러한 결정이 이루어지면 에러는 처리됩니다. 해당 스코프에서는 에러를 처리하거나 또는 함수가 throws로
선언되어야 합니다. 에러가 처리됐다는 것을 보장하기 위해 모든 에러들과 매칭되는 패턴을 가진 catch절을 사용합니다.
catch절이 패턴을 명시하지 않으면 그 절은 로컬상수 error에 모든 에러를 매칭하여 바인딩합니다. 패턴 매칭에 관한 더
자세한 정보는 Patterns 장을 참고하세요.
do {
try vend(itemNamed: "Candy Bar")
} catch VendingMachineError.InvalidSelection {
print("Invalid Selection.")
} catch VendingMachineError.OutOfStock {
print("Out of Stock.")
} catch VendingMachineError.InsufficientFunds(let amountRequired) {
print("Insufficient funds. Please insert an additions $\(amountRequired).")
}
위 예제에서, vend(itemNamed:) 함수는 에러를 던질 가능성이 있기 때문에 try 표현식 안에 표현되었습니다.
에러가 던져지면, 실행흐름은 즉시, 전파를 계속할지 결정되는 catch 절로 이동합니다. 만일 에러가 던져지지 않았다면,
vend(itemNamed:)의 반환값은 snack에 할당되고 do문의 남아있는 구문들이 실행됩니다.
4. 에러 전파 비활성화 (Disabling Error Propagation)
사실상 런타임에 에러를 던지지 않는 throwing 함수나 메서드를 알수 있는 경우가 있습니다. 이러한 경우에는
보통 사용하는 try 표현 대신에 try!(forced-try) 표현을 사용하여 throwing 함수나 메서드를 호출할 수 있습니다.
try!를 사용하여 throwing 함수나 메서드를 호출하면 error 전파가 비활성화 되고, 에러를 던지지 않는 런타임
assertion 안에서의 호출로 랩핑합니다. 만일 실제로 에러가 thrown 되면, 런타임 에러를 만나게 될것입니다.
5. 클린업 액션 지정하기 (Specifying Clearn-Up Actions)
defer 구문을 사용해서, 코드 실행흐름이 현재 코드블럭을 떠나기 직전에, 일련의 코드 집합을 실행할 수 있습니다.
에러의 발생유무와는 상관없이 반드시 실행되야할 클린업 작업을 처리할수 있습니다. 예를 들면 열려진 파일 식별자를
닫거나, 수동으로 할당된 메모리를 해제하는 작업이 될 수 있습니다.
defer 문은 현재 scope이 끝날 때까지 실행을 연기합니다. defer 키워드와 나중에 실행할 코드 구문들로 구성됩니다.
연기된 구문들은 제어흐름을 벗어나게 하는 break 또는 return, 에러를 던지는 것과 같은 코드를 포함해서는 안됩니다.
연기된 액션들은 기술된 순서의 역순으로 실행됩니다. 즉 처음 defer 문에 있는 코드는 두 번째 defer 문의 코드 다음에
실행됩니다.
func processFile(filename:String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let = try file.readline() {
// work with the file
}
// close(file) is called here, at the end of the scope.
}
}
위 예제에서는 open() 함수가 대응하는 close() 함수 호출을 보장하도록 defer문을 사용했습니다. 이 호출은
에러가 던져졌는지 유무와 상관없이 실행됩니다.
'Swift' 카테고리의 다른 글
| [swift] 성능향상 시키기 - 다이나믹 디스패치를 최소화 (0) | 2015.07.16 |
|---|---|
| [swift2] Xcode7 beta3 Swift 언어 변경사항 (0) | 2015.07.11 |
| [swift2] Swift에서 에러 처리 (Error Handling) (0) | 2015.07.10 |
| [swift2] 타입의 텍스트적인 표현 커스터마이징하기 (0) | 2015.07.01 |
| [swift2] Xcode7 beta2 Swift 언어 변경사항 (0) | 2015.06.24 |
| [swift] 문자열 Indexing과 Slicing (0) | 2015.06.23 |