* 내가 읽으려고 내 맘대로 번역한 글.
* 원문 : https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html

 

 

 

 

Error Handling

Error handling is the process of responding to and recovering from error conditions in your program. Swift provides first-class support for throwing, catching, propagating, and manipulating recoverable errors at runtime.

에러 처리는 프로그램 안에서 오류 상황으로 부터 응답하고 복구하는 방식이다.

swift는 실행시에 복구가능한 오류의 발생, 포착, 전파, 조작하는 최고수준의 지원을 제공한다.

 

Some operations aren’t guaranteed to always complete execution or produce a useful output. Optionals are used to represent the absence of a value, but when an operation fails, it’s often useful to understand what caused the failure, so that your code can respond accordingly.

일부 작업이 완전히 실행되거나 유용한 결과를 생성하는걸 항상 보장하는건 않는다.

옵셔널은 값이 없음을 표현하는데 유용하지만, 작업이 실패했을때 무엇이 실패를 발생시켰는지 아는것은 종종 유용하고.

너의 코드는 적절하게 응답할수 있다

 

As an example, consider the task of reading and processing data from a file on disk. There are a number of ways this task can fail, including the file not existing at the specified path, the file not having read permissions, or the file not being encoded in a compatible format. Distinguishing among these different situations allows a program to resolve some errors and to communicate to the user any errors it can’t resolve.

예를들어, 디스크의 파일에서 데이타를 일고 처리하는 작업을 고려해보자.

여러가지의 작업실패하는 경우가 있다.

파일이 지정된 경로에 없거나, 파일읽기 권한이 없거나, 파일이 호환되는 형식으로 인코딩 되지않았거나.

이런 다른 상황들을 구별하는것은 프로그램이 에러를 해결할수 있게 해주고,

해결할수 없는 에러는 사용자에게 알려줄수 있다.

 

NOTE

Error handling in Swift interoperates with error handling patterns that use the NSError class in Cocoa and Objective-C. For more information about this class, see Handling Cocoa Errors in Swift.

swift에서의 에러 처리는 Cocoa와 Objective-C에서의 에러 처리 패턴에 사용되는 NSError 와 상호운용된다.

 

Representing and Throwing Errors

In Swift, errors are represented by values of types that conform to the Error protocol. This empty protocol indicates that a type can be used for error handling.

swift에서, 에러는 Error 프로토콜을 구현하는 타입의 값으로 표현된다.

이 빈 프로토콜은 에러 처리에 사용될수 있는 타입임을 나타낸다.

 

Swift enumerations are particularly well suited to modeling a group of related error conditions, with associated values allowing for additional information about the nature of an error to be communicated. For example, here’s how you might represent the error conditions of operating a vending machine inside a game:

swift 열거형은 특히 연관된 에러조건의 묶음을 모델링하는데 잘 맞혀져 있고,

연관된 값으로 오류의 본질에 대한 추가정보를 알리도록 허용할수 있다.

예를 들어, 이것은 게임안에서 자동판매기 처리의 에러 조건을 어떻게 표현하는 방법이다.

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

 

Throwing an error lets you indicate that something unexpected happened and the normal flow of execution can’t continue. You use a throw statement to throw an error. For example, the following code throws an error to indicate that five additional coins are needed by the vending machine:

에러를 발생시키는것은 예상치못한 무엇인가가 발생하였고, 정상적인 실행 흐름을 계속할수 없다는걸 나타낸다.

throw 구문을 사용해서 에러를 발생시킬수 있다.

예를들어, 다음 코드는 에러를 발생시켜서 자동판매기가 추가로 5코인이 더 필요하다는걸 나타낸다.

throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

 

Handling Errors

When an error is thrown, some surrounding piece of code must be responsible for handling the error—for example, by correcting the problem, trying an alternative approach, or informing the user of the failure.

에러가 발생하면, 어떤 주변의 코드 조각들은 반드시 에러를 처리하는 책임을 진다.

예를들어 문제를 해결하거나, 다른 방안을 시도하거나, 사용자에게 실패를 알려주거나.

 

There are four ways to handle errors in Swift. You can propagate the error from a function to the code that calls that function, handle the error using a do-catch statement, handle the error as an optional value, or assert that the error will not occur. Each approach is described in a section below.

swift에서는 에러를 다루는 4가지 방법이 있다.

함수에서 그 함수를 호출한 코드에 에러를 전파하거나,

do-catch 문장으로 에러를 처리 하거나,

그 오류를 옵셔널 값으로 처리하거나,

그 오류가 발생하지 않도록 주장 할수 있다.

각각의 접근법은 아래 섹션에 기술되어 있다.

 

When a function throws an error, it changes the flow of your program, so it’s important that you can quickly identify places in your code that can throw errors. To identify these places in your code, write the try keyword—or the try? or try! variation—before a piece of code that calls a function, method, or initializer that can throw an error. These keywords are described in the sections below.

함수가 에러를 발생시키면, 너의 프로그램의 흐름은 변하고, 너는 빨리 에러를 발생시킬수있는 코드의 위치를 알아낼수 있다.

너의 코드에서 이러한 위치를 알아내기 위하여,

에러를 발생시킬수 있는 함수, 메소드, 초기화를 호출하는 코드 앞에 try, try?, try! 키워드를 사용해라.

이러한 키워드는 아래 섹션에 기술되어 있다.

 

NOTE

Error handling in Swift resembles exception handling in other languages, with the use of the try, catch and throw keywords. Unlike exception handling in many languages—including Objective-C—error handling in Swift does not involve unwinding the call stack, a process that can be computationally expensive. As such, the performance characteristics of a throw statement are comparable to those of a return statement.

swift에서 에러 처리는 다른 언어의 예외 처리와 비슷해서, try, catch, throw 키워드를 사용한다.

많은 언어의 예외 처리와 다르게 (Objective-C를 포함해서) swift에서 에러 처리는 계산비용이 비싼 콜스택 해제와 연관되어 있지 않다.

throw 문의 성능 특징은 return 문의 성능 특성과 비슷하다.

 

 

Propagating Errors Using Throwing Functions

To indicate that a function, method, or initializer can throw an error, you write the throws keyword in the function’s declaration after its parameters. A function marked with throws is called a throwing function. If the function specifies a return type, you write the throws keyword before the return arrow (->).

함수, 메소드, 초기화가 에러를 발생시킬수 있다는걸 표시하기 위하여, 함수의 선언에서 파라메터 뒤에 throws 키워드를 써라

throws 가 붙은 함수를 throwing 함수라고 부른다.

함수가 리턴 타입을 기술한다면 throws 키워드른 -> 앞에 써라

func canThrowErrors() throws -> String

func cannotThrowErrors() -> String

 

A throwing function propagates errors that are thrown inside of it to the scope from which it’s called.

throwing 함수는 내부에서 발생한 오류를 호출된 범위로 전파한다.

 

NOTE

Only throwing functions can propagate errors. Any errors thrown inside a nonthrowing function must be handled inside the function.

오직 throwing 함수만 에러를 전파할수 있다. throwing 아닌 함수에서 발생한 에러는 반드시 그 함수 내부에서 처리되어야 한다.

 

In the example below, the VendingMachine class has a vend(itemNamed:) method that throws an appropriate VendingMachineError if the requested item is not available, is out of stock, or has a cost that exceeds the current deposited amount:

아래 예제에서, VendingMachine 클래스는 vend(itemNamed:) 메소드를 가지고 있고, 이 메소드는

요청된 아이템이 유효하지 않거나, 매진되었거나, 예치된 금액보다 아이템 가격이 크면, 적절한 VendingMachineError 를 발생시킨다.

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

 

The implementation of the vend(itemNamed:) method uses guard statements to exit the method early and throw appropriate errors if any of the requirements for purchasing a snack aren’t met. Because a throw statement immediately transfers program control, an item will be vended only if all of these requirements are met.

vend(itemNamed:) 메소드 구현은 guard 구문을 사용해서 간식 구매 요구사항을 만족시키지 못하면 초기에 메소드를 빠져나가고 적절한 에러를 발생시킨다. throw 구문은 즉시 프로그램 제어를 옮기기 때문에,

이러한 요구사항이 모두 만족할때만 아이템이 판매될수 있다.

 

Because the vend(itemNamed:) method propagates any errors it throws, any code that calls this method must either handle the errors—using a do-catch statement, try?, or try!—or continue to propagate them. For example, the buyFavoriteSnack(person:vendingMachine:) in the example below is also a throwing function, and any errors that the vend(itemNamed:) method throws will propagate up to the point where the buyFavoriteSnack(person:vendingMachine:) function is called.

vend(itemNamed:) 메소드는 발생하는 에러를 전파하기 때문에,

이 메소드를 호출하는 모든 코드는 반드시 그 에러들을 처리해야 한다 (do-catch, try?, try! 구문을 사용해서)

또는 그 에러들을 계속 전파할수 있다.

예를 들어, 아래 예제의 buyFavoriteSnack(person:vendingMachine:) 는 역시 throwing 함수이고,

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

 

In this example, the buyFavoriteSnack(person: vendingMachine:) function looks up a given person’s favorite snack and tries to buy it for them by calling the vend(itemNamed:) method. Because the vend(itemNamed:) method can throw an error, it’s called with the try keyword in front of it.

이 예제에서, buyFavoriteSnack(person: vendingMachine:) 함수는 주어진 사람이 좋아하는 스낵을 찾고,

vend(itemNamed:) 메소드를 호출하여 구매하려고 시도한다.

vend(itemNamed:) 메소드는 에러를 발생시킬수 있기 때문에, 호출할때 앞에 try 키워드를 썼다.

 

Throwing initializers can propagate errors in the same way as throwing functions. For example, the initializer for the PurchasedSnack structure in the listing below calls a throwing function as part of the initialization process, and it handles any errors that it encounters by propagating them to its caller.

throwing 초기화는 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

You use a do-catch statement to handle errors by running a block of code. If an error is thrown by the code in the do clause, it is matched against the catch clauses to determine which one of them can handle the error.

Here is the general form of a do-catch statement:

do-catch 구문을 사용하여 코드의 실행블록으로 에러를 처리할수 있다.

do 절에서 코드에 의해 에러가 thrown 되면, 어떤 catch 절에서 에러를 처리할수 있는지 판단한다.

여기 일반적인 do-catch 구문 형태가 있다.

do {
	try expression
	statements
} catch pattern 1 {
	statements
} catch pattern 2 where condition {
	statements
} catch {
	statements
}

 

You write a pattern after catch to indicate what errors that clause can handle. If a catch clause doesn’t have a pattern, the clause matches any error and binds the error to a local constant named error. For more information about pattern matching, see Patterns.

catch 뒤에 패턴을 작성하여 어떤 에러를 처리할수 있는지 나타낸다.

catch 절에 패턴을 적지 않으면, 모든 에러를 처리하고 error 라는 지역상수에 에러를 연결한다.

 

For example, the following code matches against all three cases of the VendingMachineError enumeration.

예를 들어, 아래 코드는 3가지 경우의 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."

 

In the above example, the buyFavoriteSnack(person:vendingMachine:) function is called in a try expression, because it can throw an error. If an error is thrown, execution immediately transfers to the catch clauses, which decide whether to allow propagation to continue. If no pattern is matched, the error gets caught by the final catch clause and is bound to a local error constant. If no error is thrown, the remaining statements in the do statement are executed.

위의 예제에서, buyFavoriteSnack(person:vendingMachine:) 함수는 try 표현식과 같이 호출된다. 왜냐면 에러를 throw 할수 있으니까.

에러가 thrown 되면, 실행은 즉시 catch 절로 이동되고, 계속 진행하기 위해 전파를 허용할지 결정하게 된다.

만약 일치하는 패턴이 없으면, 에러는 마지막 catch 절에 포착되고 지역상수 error 에 바인딩된다.

만약 에러가 thrown 되지 않으면, do 구문의 나머지 문장들이 실행된다.

 

The catch clauses don’t have to handle every possible error that the code in the do clause can throw. If none of the catch clauses handle the error, the error propagates to the surrounding scope. However, the propagated error must be handled by some surrounding scope. In a nonthrowing function, an enclosing do-catch clause must handle the error. In a throwing function, either an enclosing do-catch clause or the caller must handle the error. If the error propagates to the top-level scope without being handled, you’ll get a runtime error.

do 절에서 throw 할수 있는 모든 에러를 catch 절이 다 처리할 필요는 없다.

에러를 처리할수 있는 catch 절이 없으면, 그 에러는 주변 범위로 전파된다.

하지만 전파된 에러는 반드시 주변 범위에서 처리되어야만 한다.

throwing 안하는 함수에서 do-catch 절은 반드시 에러를 처리해야만 한다.

throwing 함수에는 do-catch 절이나 호출자가 반드시 에러를 처리해만 한다.

만약 에러가 처리되지 않고, 최상단 범위까지 전파되면 실행시 에러가 발생한다.

 

For example, the above example can be written so any error that isn’t a VendingMachineError is instead caught by the calling function:

예를 들어, 위의 예제는 VendingMachineError 가 아닌 모든 에러는 함수를 호출함으로써 대신 catch 되도록 작성할수 있다.

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

 

In the nourish(with:) function, if vend(itemNamed:) throws an error that’s one of the cases of the VendingMachineError enumeration, nourish(with:) handles the error by printing a message. Otherwise, nourish(with:) propagates the error to its call site. The error is then caught by the general catch clause.

nourish(with:) 함수에서, vend(itemNamed:) 가 VendingMachineError 열거형중에 하나를 throw 한다면,

nourish(with:) 는 메세지를 출력하여 그 에러를 처리한다.

그렇지 않으면 (VendingMachineError 가 아니면) nourish(with:) 는 호출자에게 에러를 전파한다.

에러는 그때서야 일반적인 catch 구문에 의해서 catch 된다.

 

Converting Errors to Optional Values

You use try? to handle an error by converting it to an optional value. If an error is thrown while evaluating the try? expression, the value of the expression is nil. For example, in the following code x and y have the same value and behavior:

try? 를 사용해서 에러를 optional 값으로 변환하여 처리할수 있다.

try? 표현식으로 평가될때 에러가 throw 되면, 그 표현식의 값은 nil 이다.

예를 들어, 아래 코드에서 x 와 y 는 같은 값과 동작을 갖는다.

func someThrowingFunction() throws -> Int {
	// ...
}

let x = try? someThrowingFunction()

let y: Int?
do {
	y = try someThrowingFunction()
} catch {
	y = nil
}

 

If someThrowingFunction() throws an error, the value of x and y is nil. Otherwise, the value of x and y is the value that the function returned. Note that x and y are an optional of whatever type someThrowingFunction() returns. Here the function returns an integer, so xand y are optional integers.

만약 someThrowingFunction() 에러를 throw 하면, x와 y의 값은 nil 이다.

그렇지 않으면 x와 y의 값은 함수의 리턴값이다.

someThrowingFunction() 이 리턴하는 타입이 무엇이던지간에 x와 y 둘다 옵셔널이라는걸 주목해라.

함수가 정수를 리턴하면 x와 y 는 optional 정수다.

 

Using try? lets you write concise error handling code when you want to handle all errors in the same way. For example, the following code uses several approaches to fetch data, or returns nil if all of the approaches fail.

모든 에러를 같은방식으로 처리하기를 원할때 try? 를 사용하는것은 에러 처리 코드를 간결하게 쓸수 있도록 해준다.

예를 들어, 아래 코드는 여러가지 방식으로 데이타를 가져오고, 그 방식이 실패하면 nil 을 리턴한다.

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

 

Disabling Error Propagation

Sometimes you know a throwing function or method won’t, in fact, throw an error at runtime. On those occasions, you can write try! before the expression to disable error propagation and wrap the call in a runtime assertion that no error will be thrown. If an error actually is thrown, you’ll get a runtime error.

가끔 throw 함수나 메소드가 실제로 실행시에 에러를 throw 하지 않는걸 알수 있다.

그런 경우에, 표현식 앞에 try! 써서 에러 전파를 막을수 있고, 에러가 throw 되지 않는 런타임 assertion을 호출을 wrap 한다.

실제로 에러가 throw 되면, 실행시에 에러 발생한다.

 

For example, the following code uses a loadImage(atPath:) function, which loads the image resource at a given path or throws an error if the image can’t be loaded. In this case, because the image is shipped with the application, no error will be thrown at runtime, so it is appropriate to disable error propagation.

예를들어, 아래 코드는 loadImage(atPath:) 함수를 사용하여, 이미지 리소스를 로드하고, 이미지를 로드할수 없으면 에러를 throw 한다.

이 경우 이미지는 어플리케이션에 포함되어 있기 때문에, 실행시에 에러가 throw 되지 않으니까,

에러 전파를 비활성화 하는것은 적절하다.

let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")

 

Specifying Cleanup Actions

You use a defer statement to execute a set of statements just before code execution leaves the current block of code. This statement lets you do any necessary cleanup that should be performed regardless of how execution leaves the current block of code—whether it leaves because an error was thrown or because of a statement such as return or break. For example, you can use a defer statement to ensure that file descriptors are closed and manually allocated memory is freed.

현재 실행되는 블록을 떠나기 직전에 일련의 문장들을 실행시키기 위해서 defer 문을 이용할수 있다.

이것은 현재 실행되는 블록을 어떻게 떠나는지에 관계없이-에러가 발생했거나, return 또는 break 문 때문이거나-

실행되어야 하는 정리를 할수 있게 해준다.

예를 들어, defer 문을 이용하여 파일을 닫고 수동으로 메모리를 정리할수 있다.

 

A defer statement defers execution until the current scope is exited. This statement consists of the defer keyword and the statements to be executed later. The deferred statements may not contain any code that would transfer control out of the statements, such as a break or a return statement, or by throwing an error. Deferred actions are executed in the reverse of the order that they’re written in your source code. That is, the code in the first defer statement executes last, the code in the second defer statement executes second to last, and so on. The last defer statement in source code order executes first.

defer 문은 현재 범위가 끝날때까지 실행을 유보한다.

이 문을 defer 키워드와 나중에 실행될 문장들로 구성된다.

유보된 문장들은 break, return, throw error 같은 흐름을 밖으로 옮기는 문장들을 포함할수 없다.

유보된 동작들은 소스코드에 쓰여진 순서의 반대 순서로 실행된다.

이것은 맨처음 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.
	}
}

 

The above example uses a defer statement to ensure that the open(_:) function has a corresponding call to close(_:).

위의 예제는 defer 문을 사용하여 open(_:) 함수가 close(_:) 호출과 일치하도록 보증한다.

 

NOTE

You can use a defer statement even when no error handling code is involved.

오류 처리 코드가 없을때에서 defer 문을 사용할수 있다.

반응형

'iOS 초보' 카테고리의 다른 글

[swift5.1번역] 19.Nested Types  (0) 2019.11.26
[swift5.1번역] 18.Type Casting  (0) 2019.11.25
[swift5.1번역] 16.Optional Chaining  (0) 2019.10.11
[swift5.1번역] 15.Deinitialization  (0) 2019.09.17
[swift5.1번역] 14.Initialization  (0) 2019.09.04
Posted by 돌비
,