defer 문 이해하기

Defer in Swift 2.0

Swift 2.0 에서 새롭게 등장한 defer 구문을 살펴보자. defer문은 다음 코드를 현재 스코프를 벗어날 때 실행한다는 예약이다. 무슨 말인고 하니,


func doit() { defer{ print("4")} print("2") print("3") } print("1") doit() print("5")

위 코드의 실행 결과는 순서대로 1 -> 2 -> 3 -> 4 -> 5이다. defer는 이후에 받는 블럭을 당장 실행하지 않고 현재 블럭 범위가 종료될 때 실행한다. 이는 단순히 실행의 우선순위를 변경하는 것외에 매우 요긴하게 쓰일 수 있다.

func logging() {
    let file = openFile(...)

    guard let statusMsg = fetchNetworkStatus() where statusMsg != "OK" { return }

    file.write(statusMsg)
    file.close()
}

위 코드는 네트워크 상태 메시지를 가져와서 정상이 아닌 경우 로그를 남기는데, 정상 메시지를 받은 경우에는 리턴하고 있다. 그러면 열어놓은 파일 핸들러를 처리하지 않고 함수가 바로 종료된다. 물론 else 문 내에서 파일을 닫아주는 방법도 있는데, 이러한 가드가 여러 개라면 일일이 붙여주는 것도 귀찮아진다.

defer는 이럴 때 요긴하게 사용된다. 마치 파이썬의 컨텍스트 매니저와 같이 현재 스코프를 벗어나는 시점에 동작할 것을 미리 정할 수 있는 것이다. 아마 예외에 의해 중단되는 블럭에 대해서도 동작할 것이라 생각된다.

func logging() {

    let file = openFile(...)
    defer { file.close() }

    guard let statusMsg = fetchNetworkStatus() where statusMsg != "OK" { return }

    file.write(statusMsg)
}

정상 종료의 경우에도 defer에 의해 예약된 코드가 실행되므로 함수 내부에서는 이 함수가 종료될 때 파일이 닫힌다는 것을 보장받으므로, 파일 닫는 코드를 명시할 필요가 없다.

다음의 코드는 매우 중요하다.

func fail() throws -> () {
    throw NSError(domain:"U", code:1, userInfo:nil)
}

do {
    defer { print("x") }
    try fail() // 이 부분에서 실패한다. 
    print("y")
} catch  {
    print("fail")
} 

임의로 정의한 함수 fail()은 무조건 예외를 던지는 함수이다. do 블럭 내에서 이 함수를 호출하기전에 defer를 이용해서 특정 문구를 출력하게 했다. try 문에서 이 함수는 예외를 던졌으므로, 이후 코드가 실행되지 않고 즉시 블럭을 탈출하게 되는데, 이 때 defer에서 지정한 블럭이 실행된다.

이는 파일을 열거나, 데이터베이스 연결이나 네트워크 연결을 만들어 놓고 작업하는 중 예외를 만나는 경우에도 안전하게 리소스를 닫는 처리를 할 수 있다는 것을 의미한다.

보너스

defer 문을 이용해서 준비한 코드는 스택에 쌓이게 되어, 해당 스코프를 빠져나오는 시점에 정의된 역순으로 실행된다.

do {
    defer { print("1") }
    defer { print("2") }
}
//  2
//  1