[최종 수정일 : 2018.09.04]
원문 : 애플 개발 문서 Swift 4.2 Language Guide - Error Handling
오류 처리(Error Handling)
오류 처리(Error handling)은 프로그램에서 오류 상태를 복구(recovering)하고 응답(responding)하는 과정입니다. Swift는 실시간으로 오류를 복구하는 던지기(throwing), 잡기(catching), 전달하기(propagating), 조작하기(manipulation) 일류 클래스(first-class)를 지원합니다
일부 작업은 항상 실행이 완료되거나 유용한 결과물을 만들어 내지는 못합니다. 옵셔널은 값이 없는 것을 표현하기 위해 사용되지만, 까끔식 동작이 실패할때, 무엇때문에 실패했는지 인해하는데 유용하며, 그에 알맞는 코드로 대응할 수 있습니다.
예를들어, 디스크에서 파일 데이터를 읽고 처리하는 과정을 생각해 봅시다. 지정된 위치에서 파일이 존재하지 않는 파일을 포함하거나, 파일에 대한 권한이 없거나 파일이 호환되지 않는 포멧으로 인코딩 되어 있는 경우에 이 작업들은 실패할 수 있습니다. 이러한 서로 다른 상을 구분해서 프로그램의 일부 오류를 해결하고 해결할 수 없는 오류는 사용자에게 알려줍니다.
주의
Swift에서의 오류 처리는 Cocoa와 Objective-C 에서NSError
클래스를 사용한 오류처리와 호환됩니다. 이 클래스에 대한 자세한 정보는, Swift에서의 Cocoa 오류 처리하기)Handling Cocoa Errors in Swift)를 보세요.
오류를 표시하고 던지기(Representing and Throwing Errors)
Swift에서, 오류는 Error
프로퍼컬을 준수(conform)하는 타입의 값으로 표현됩니다. 빈 프로토콜은 오류처리에 대해 사용될 수 있는 타입임을 가리킵니다.
Swift 열거형은 특히 전달되야하는 오류의 특징(nature)에 관해서 추가적인 정보를 허용하는 연관된 값들로 관련된 오류 조건의 그룹을 모델링 하기에 적합합니다. 예를들어, 다음은 게임내의 자동 판매기(vending machine) 기능의 오류 조건을 나타내는 방법입니다.
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
예상치 않게 오류를 던지고 일반적인 실행을 계속 할수 없다는 것을 가리킵니다. 오류를 던지기 위해 throw
문을 사용합니다. 예를 들어, 다음에 오는 코드는 자동판매기에서 동전 5개가 필요하다는 것을 가리키기 위해 오류를 던집니다.
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
오류 처리하기(Handling Errors)
오류를 던질때, 코드의 주변에서 오류를 처리해야 하는 책임이 있습니다.- 예를 들어, 문제를 해결하기, 다른 접근법 시도, 사용자에게 실패 정보를 알리기
Swift에서 오류처리하는 4가지 방법이 있습니다. 함수의 오류를 함수를 호출하는 코드로 전달 할 수 있으며, do-catch
문을 사용하여 오류를 처리하며. 옵셔널 값으로 오류를 처리 하거나 오류가 발생하지 않도록 assert
사용합니다. 각 접근법은 아래 섹션에 설명되어 있습니다.
함수가 오류를 던질때, 프로그램의 흐름이 바뀌며, 코드에서 오류를 던질수 있는 곳에서 빨리 식별할 수있는 것이 중요합니다. 코드에서 이런 위치를 식별하기 위해, 오류를 던질수 있는 함수, 메소드 , 초기화 코드 앞에 try
키워드(try?
또는 try!
도 가능) 를 작성합니다. 이 키워드는 아래 섹션에서 설명되어 있습니다.
주의
Swift에서 오류처리(Error handling)는try, catch, throw
키워드를 사용하는 다른 언어의 예외처리(exception handling)와 유사합니다. 다른 언어의 예최 처리와 다른 점은 Swift에서의 오류 처리는 호출 스택(call stack)을 해제하는(unwinding) 것은 포함되지 않으며, 처리하는 계산 비용이 비쌉니다. 이와같이,throw
문의 성능상 특징은return
문과 비교가능합니다.
던지는 함수를 사용하여 오류 전달하기(Propagating Errors Using Throwing Functions)
함수, 메소드, 초기화가 오류를 던질수 있는 것을 가리키기 위해, 함수 선언에서 매개변수 뒤에 throws
키워드를 작성합니다. throws
로 표시된 함수를 던지는 함수(throwing function)라고 합니다. 함수가 반환 타입을 지정하는 경우, 반환 화살표(->
) 앞에 throws
키워드를 작성합니다.
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
던지는 함수(throwing function)는 호출된 범위 안쪽으로 전달 받은 오류를 던집니다.
주의
던지는 함수(throwing functions)만 오류를 전달 할수 있습니다. 모든 오류는 던지지않는 함수(nonthrowing function) 안쪽에서 던지며 그 함수 안에서 처리해야만 합니다.
아래 예제에서, 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:)
메소드의 구현은 스낵(snack) 구입 요건 중 하나라도 충족되지 않은 경우에, 메소드를 일찍 탈출하거나 적절한 오류를 던지기 위해 guard
문을 사용합니다. throw
문은 프로그램 제어권을 즉시 편환해서 넘겨주기 때문에, 항목은 이러한 요구사항이 만족됬을때에만 판매 될것입니다.
vend(itemNamed:)
메소드는 던지는 모든 오류를 전달하기 때문에, 메소드를 호출하는 코드에서 오류(do-catch
문, try?
, try!
)를 처리하거나 계속 전달해야 합니다. 예를 들어, 아래 예제에서 buyFavoriteSnack(person:vendingMachine:)
는 던지는 함수(throwing function)이고, buyFavoriteSnack(person:vendingMachine:)
함수가 호출되는 곳에서 vend(itemNamed:)
메소드는 위로 전달할 것입니다.
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:)
메소드를 호출해서 해당 항목을 구매하려고 합니다. vend(itemNamed:)
메소드는 오류를 던질수 있기 때문에, 앞쪽에 try
키워드로 호출됩니다.
던지는 초기화(throwing initializers)는 던지기 함수(throwing functions)와 같은 방법으로 오류를 전달 할 수 있습니다. 예를 들어, 아래 목록에 있는 PurchasedSnack
구조체에 대한 초기화는 초기화 과정에서 던지는 함수(throwing function)를 호출하고, 호출자에게 전달하여 오류를 처리하도록 합니다.
struct PurchasedSnack {
let name: String
init(name: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(itemNamed: name)
self.name = name
}
}
Do-Catch 를 사용해서 오류처리하기(Handing Errors Using Do-Catch)
코드 블럭을 실행해서 오류를 처리하기 위해 do-catch
문을 사용합니다. do
절(clause) 안쪽 코드에 의해 오류가 던져지는 경우, 오류를 처리할 수 있는 것들 중 하나를 결정하기 위해 일치하는 catch
절을 사용합니다.
다음은 do-catch
문의 일반적인 형식입니다.
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} catch {
statements
}
클로져가 오류를 처리할 수 있다는 것을 가리키기 위해 패턴 뒤에 catch
를 작성합니다.. catch
절과 일치하는 패턴이 없는 경우, 그 절(clause)은 모든 오류와 일치하고 그 오류를 지역 상수 error
로 연결(binds)됩니다. 패턴 매칭(pattern matching)에 대한 자세한 내용은 패턴(Patterns)을 보세요.
예를들어, 다음에 오는 코드는 VendingMachinError
열거형의 모든 3가지 경우(case)와 일치합니다.
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
절에 의해 잡히고 지역 상수 error
로 연결됩니다. 오류없이 던지는 경우, do
문의 남은 구문들이 실행됩니다.
catch
절은 throw
할수 있는 do
절 안의 코드는 모든 오류 가능한 것들을 처리하지 않습니다. 오류를 처리하는 catch
절이 없는 경우, 오류는 주변 영역으로 전달됩니다. 하지만, 전달된 오류는 반드시 주변 범위에서 처리 됩니다. 던지지 않는(nonthrowing) 함수에서는, do-catch
절 안에서 반드시 오류를 처리합니다. 던지는(throwing) 함수에서는, do-catch
절 안이나 호출한 곳에서 반드시 오류를 처리합니다. 오류를 처리하는 곳 없이 최상위 단계 범위까지 전달하는 경우, 런타임 오류가 발생할 것입니다.
예를 들어, 위 예제의 모든 오류를VendingMachineError
대신 호출하는 함수에서 잡도록 작성할 수 있습니다.
func nourish(with item: String) throws {
do {
try vendingMachine.vend(itemNamed: item)
} catch is VendingMachineError {
print("Invalid selection, out of stock, or not enough money.")
}
}
do {
try nourish(with: "Beet-Flavored Chips")
} catch {
print("Unexpected non-vending-machine-related error: \(error)")
}
// Prints "Invalid selection, out of stock, or not enough money."
nourish(with:)
함수에서, vend(itemNamed:)
가 VendingMachineError
열거형의 케이스(case) 중 하나로 오류를 던지는 경우, nourish(with:)
는 메시지를 출력하여 오류를 처리합니다. 반면에, nourish(with:)
는 오류를 호출한 곳으로 전달합니다. 오류가 발생하면 일반적인catch
절로 잡습니다.
옵셔널 값으로 오류 변환하기(Converting Error 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)
가끔씩 던지는 함수나 메소드를 모르는 경우에, 사실상 실행중(runtime)에 오류를 던집니다(throw). 이러한 경우에, 표현식 앞에 오류를 전달하지 않거나 실행중에 오류를 던지지 않도록 assertion의 호출을 감싸기 위해 try!
을 작성할 수 있습니다.
예를들어, 다음에 오는 코드는 주어진 경로에서 이미지 리소스를 불러오거나 이미지를 불러오지 못하는 경우 오류를 던지는 loadImage(atPath:)
함수를 사용합니다. 이 경우에, 이미지는 앱과 함게 제공되기 때문에, 실행중에 오류를 던지지 않을것이며, 오류 전달하지 않는것이 적절합니다.
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
## 정리 작업 지정하기(Specifying Cleanup actions)
현재 코드의 블럭의 코드 실행이 끝나기 직전에 설정한 구문들을 실행하기 위해, defer
문을 사용합니다. 이 구문은 사용하면 현재 코드의 블럭을 끝나는 방법과 관계없이 필요한 정리 작업을 할 수 있습니다. - 오류가 던저졌거나 return
또는 break
문으로 끝나게 됩니다. 예를 들어, 파일 설명자가 종료하고 수동으로 할당된 메모리가 해제되는지 확인하기 위해, defer
문을 사용할 수 있습니다.
defer
문은 현재 범위가 종료될때까지 실행을 미룹니다. 이 구문은 defer
문과 나중에 실행될 구문으로 구성되어 있습니다. 미뤄진 구문들은 break
나 return
문, 오류 던지기(throwing) 처럼, 구문의 바깥으로 제어를 넘기는 어떤 코드도 포함할 수 없습니다. 미뤄진 동작은 소스코드로 작성된 역순으로 실행됩니다. 이것은, 첫번째 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.
}
}
위 예제는 open(_:)
함수가 close(_:)
를 적절히 호출하는 것을 보증하는 defer
문을 사용합니다.
주의
오류 처리가 없는 경우에도defer
문을 사용할 수 있습니다.
'Swift > Language Guide' 카테고리의 다른 글
Protocols (0) | 2018.09.18 |
---|---|
Extensions (0) | 2018.09.18 |
Nested Types (0) | 2018.09.18 |
Type Casting (0) | 2018.09.18 |
Optional Chaining (0) | 2018.09.18 |
Deinitialization (0) | 2018.09.18 |
Initialization (0) | 2018.09.17 |
Inheritance (0) | 2018.08.30 |