2015년 6월 23일 화요일

Swift 2.0 - defer 문

아마도 Swift 2.0 들어서 문법적으로 가장 강력한 기능이 아닐까 생각하는 것이 바로 이 defer 문이다. 아 물론 개인적인 생각일 뿐이다... '-';;

defer는 오류 처리를 위해 '정상상황 즉 오류가 발생하지 않을 때의 뒷처리(?)'를 정의하기 위한 용도이다. 다르게 표현하자면 "이걸 실행하고는 싶지만 지금은 아니야" 정도일까. 말로는 한참 떠들어대도 이해가 안될테니 -_- 그냥 예제 코드를 보자.
func testFunc(conditionA: Bool, _ conditionB: Bool) -> Bool {
    print("Checking Condition A")
    
    guard conditionA else {
        return false
    }
    
    defer {
        print("From Condition A")
    }
    
    print("Checking Condition B")
    
    guard conditionB else {
        return false
    }
    
    defer {
        print("From Condition B")
    }
    
    print("Finish")
    
    return true
}
testFunc 라는 함수를 하나 정의하고 있다. 이 함수는 인자 두 개를 받아서 둘 다 참(true)일 경우에만 true를 리턴한다. 그 이외의 경우엔 false를 리턴한다. 뭐 어려울 건 없다. 그저 defer로 묶여있는 두 개의 블럭이 특이할 뿐이다.

자 이제 이 함수를 호출해 보자. 두 인자 모두 false로 호출해봤다.
testFunc(false, false)
결과는 false를 리턴하고 아래와 같은 로그가 콘솔(혹은 로그창)에 찍힌다.
Checking Condition A
여기까지는 뭐 대충 defer를 몰라도 유추가 가능한 부분이다. 첫 번째 guard 문에 의해 반환되었기 때문이다.

이제 다른 방식으로 해 보자. 이번엔 아래 처럼 두 번째 인자를 false로 놓고 호출하면
testFunc(true, false)
당연히 이번에도 false를 리턴하지만 아래와 같은 로그가 남는다.
Checking Condition A
Checking Condition B
From Condition A
마지막에 찍힌 내용은 뭔가 순서 상 이상한 것 같다고 느껴지는가? 마지막 줄 로그는 첫 번째 defer​ 블럭이 실행되면서 찍힌 것이다. 하지만 이 블럭은 블럭이 위치하는 순서가 아닌 함수 실행 종료 시점에서 실행되었다는 것을 알 수 있다.

마지막으로 이제 둘 다 true를 줘서 호출해보자.
testFunc(true, true)
true가 리턴되는 결과이다. 이번 예제의 로그는 아래와 같다.
Checking Condition A
Checking Condition B
Finish
From Condition B
From Condition A
두 개의 defer 블럭은 모두 함수가 종료되는 시점에서 실행되었다.

아마도 이 정도의 예제면 defer가 어떻게 실행되는지 알 수 있을 것이다.

defer

defer의 기능은 scope(즉 블럭 내부. 내부 블럭이거나 함수 혹은 메소드 단위) 처리가 끝날 때(혹은 리턴 될 때) 호출되는 명령을 등록하기 위한 것이다. 즉, defer로 감싼 구문은 defer가 소속된 블럭에서 탈출 할 때 실행된다. 동적으로 처리가 되기때문에 defer 구문이 호출되지 않았다면 당연히 defer로 감싼 구문은 함수가 종료되어도 호출되지 않는다.

따라서 위 예제의 경우 "From Condition A" 혹은 "From Condition B" 라는 로그를 찍는 코드는 위 testFunc 함수가 종료될 때만 호출된다. 그리고 이 두 로그는 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.
    }
}
"close(file)" 이라는 코드가 defer로 묶여 있다. 따라서 이 코드는 바로 실행되지 않는다. 대신 "if exists(filename)"으로 시작되는 블럭이 끝날 때 "close(file)" 이 실행된다. 혹은 file.readline() 에서 특정 에러(exception)가 발생해서 함수가 종료될 경우에도 "close(file)" 이 실행된다.

만약 이 defer가 제공되지 않는다면 어떻게 해야 했을까.
func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        
        do {
            while let line = try file.readline() {
                /* Work with the file. */
            }
        }
        catch {
            close(file)
        }

        close(file)
    }
}
아마도 defer가 제공되지 않는다면 이런 코드 형태로 코딩해야 될 것이다. 중복 코드가 있는데 아마도 이 중복코드를 해결하기엔 좀 골치아플 것 같다. 물론 위 코드도 Swift 2.0 하에서만 가능한 do-try-catch 구문이 쓰이긴 했지만 기존 Swift 1.x 코드와 큰 차이는 없을 것 같다.

마무리

defer는 정말 강력한 명령어이자 문법이라고 생각한다. 물론 코드 실행 순서가 흐트러지기 때문에 어지러울 수도 있지만, 적어도 많은 코드를 줄일 수 있을거라 생각한다.

내가 아는 언어 중에서 블럭 탈출 때만 호출되는 코드를 프로그래밍 할 수 있는 언어는 없었다. =_=;; 어휴 젠장 스위프트 만세다 제기랄...

그나저나 뭔가 변할 가능성이 있지 않을까 고민되는게 있다. 위의 testFunc(true, true) 예제 결과를 보면 defer의 실행 순서가 역순이다. 마치 스택에 해당 블럭을 클로져로 넣어놓고 꺼내면서 실행시킨다는 기분이 든다. 하지만 그렇다고 이게 고정일 것 같지는 않다. 실행 순서는 굉장이 중요한 이슈다. 공식 책(?)에도 순서에 관한 내용이 없는 것을 보면 버전이 올라가면서 변경될 여지가 없는 것도 아닐 것이다. 그러니 이 블로그의 내용을 맹신하지 말자 -_-;;

[관련글] 스위프트(Swift) 가이드

댓글 없음 :