16. 에러 처리(Error handling, defer)

에러처리는 프로그램의 에러 조건에서 응답하고 대응하는 프로세스이다. 일부 작업은 항상 실행을 완료하거느 유용한 출력을 생성한다고 보장되지 않는다. 작업이 실패할 경우 코드가 그에 따라서 응답하도록 에러의 원인을 이해하는 것이 유용한 경우가 많고, 이를 위해서 에러핸들링을 사용한다.

에러의 표현과 스로잉(Throwing)

  1. 에러는 Error 프로토콜을 준수하는 타입의 값으로 표현된다. 열거형은 관련된 에러의 조건을 모델링하는데 매우 적합하며, 관련 값을 통해 에러의 특성 및 정보를 유저에게 전달할 수 있다.
enum VendingmachineError : Error {
	case invalidSelection
    case insufficientFunds(coinsNeeded : Int)
    case outOfStock
}

//에러가 발생하면 예상치 못한 일이 발생하여 정상적 코드흐름이 불가능함을 나타낼 수 있다. 
//throw구문을 사용해서 에러를 발생시킨다.
throw VendingMachineError.insufficientFunds(coinsNeeded : 5)
//다음과 같이 에러를 명시할 수 있다.

에러 처리

  1. 에러가 발생할때는 문제를 수정하거나 다른방법을 시도하거나, 사용자에게 에러를 알리는 방법으로 에러를 처리해야 한다. 스위프트에서는 에러를 처리하는 4가지 방법이 있다.
  • 함수에서 해당 함수를 호출하는 코드로 에러를 전파
  • do-catch 구문을 사용
  • 옵셔널 값으로 에러를 처리
  • 에러가 발생하지 않을 것이라고 주정
  1. 스로잉 함수를 이용한 에러 전파를 명시할 수 있다.throws로 표시된 함수는 throw 함수라고 한다.
func canThrowErrors() throws -> String {
 	//statement
}
//위의 메서드는 에러를 스로잉하는 함수이다. 
//throwing함수는 에러를 전파만 가능하다. 던지기 선언이 되지 않은 함수에서 발생하는 모든 에러는
//모두 함수 내에서 처리되어야 한다.

struct Item {
	var price : Int
    var count : Int
}

class VendingMachine {
	var coinDeposit = 0
    
    func vend(itemNamed name : String) throws {
    	guard let item = inventory[name] else {
        	throws VendingMachineError.invalidSelection
        }
    }
}
//위에서 보는 것처럼 throw함수를 통해서 직접 에러를 스로잉 할 수 있다.

func buyFavoriteSnack(person : String, vendingMachine : VendingMachine) throws {
	let snackName = snackName ?? "Candy Bar"
    try vendingMachine.vend(itemNamed : snackNamed)
}
//다음과 같이 스로잉 함수를 호출할때는 앞에 try키워드를 붙여줘서 이 함수가 스로잉 함수임을 나타내준다.
  1. Do-catch를 사용하여 에러 처리가 가능하다. do-catch 구문을 통해서 코드 블럭을 실행하고 에러를 처리한다. 에러가 do절에서 발생되면 catch절과 비교하여 에러를 처리할 항목을 결정한다.
do {
	try expression
} catch pattern 1 {
  // statement
} catch pattern 2 {
	//statement
} catch pattern 3 {
	//statement
}
//처리할 수 있는 에러가 무엇인지 나타내기 위해서 catch 뒤에 패턴을 작성한다. 

var vendingMachine = VendingMachine()
vendingMachine.coinDeposited = 8
do {
	try buyFavoriteSnack()
} catch VendingMachineError.invalidSelection {
	print("invalid Selection")
} catch VendingMachineError.outOfStock {
	print("Out of stock")
} catch VendingMachineError.insufficient(let coinsNeeded) {
	print("insufficient coins")
} catch {
	print("unexpected Error")
}

//다음과 같이 모든 에러 상황에 대한 클로저를 작성하여 수행작업을 지정할 수 있다.
//try를 통해 호출하고 에러가 발생 시 catch 클로저로 진행된다.
//catch절은 do 절에서 발생가능한 모든 에러를 처리할 필요는 없다.
  1. 에러를 옵셔널 값으로 변환할 수 있다. 다음의 작업을 위해서 try?를 사용한다. try?는 호출 간 에러가 발생 시 표현식의 값을 nil로 반환한다.
func someThrowingFunction() throws -> Int {
	//statement
}

let x = try? someThrowingFunction()

do {
	y = try someThrowingFunction()
} catch {
	y = nil
}

//다음 함수에서 someThrowingFunction()이 에러를 발생시키면, x와 y의 값은 nil이다. 그렇지 않은 경우는 x와 y가 옵셔널 정수값을 가진다.
//try?를 사용하면 모든 에러를 같은 방식으로 처리하려는 경우 간결하게 작성 가능하다.

func fetchData() -> Data? {
	if let data = try? fetchDataFromDisk() {
    	return data
    }
    if let data = try? fetchDataFromServer() {
    	return data
    }
    return nil // 에러를 다음과 같이 간단하게 처리한다.
}
  1. 에러 전파 비활성화가 가능하다. 스로잉 함수 또는 메서드가 실제로 런타임 에러가 없다는 것을 알고 있는 경우 에러 전파 비활성화를 위해서 try!를 작성할 수 있고, 호출이 가능하다. 이 경우 에러가 발생하면 런타임 에러가 난다.
let photo = try! loadImagE(atPath : "./Resource/John Appleseed.jpg")
//옵셔널 강제 언래핑과 비슷하게 에러도 강제 언래핑 할 경우 런타임 오류가 난다.

정리작업 지정(defer)

  1. 코드 실행 간 현재 블럭이 종료되기 직전 defer구문을 사용하고 일련의 구문을 시행한다. 이 구문을 통해서 에러가 발생하여 종료되거나 return, break등의 구문 종료에 대해 방식에 상관없이 필요한 정리 수행이 가능하다.
  2. defer구문은 현재 범위가 종료될 때까지 실행을 연기한다.
func processFile(filename : String) throws {
	if exist(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 scope
        //여기에서 defer에서 정의한 클로저가 실행된다.
    }
}

좋은 웹페이지 즐겨찾기