defer 이해하기

|

이 글은 공부 중에 작성하는 글입니다.
정정이 필요한 내용은 댓글로 알려주시면 감사하겠습니다 :)


안녕하세요 오동나무입니다.

오늘은 defer에 대해서 알아보도록 하겠습니다. 포스팅은 The Swift Programming Language - Statements문서와 야곰의 책으로 공부한 내용을 바탕으로 작성합니다. 그리고 [How "defer" operator in Swift](https://medium.com/@sergeysmagleev/how-defer-operator-in-swift-actually-works-30dbacb3477b)도 참고하였습니다.



defer

image

defer는 사전의 의미대로 작업을 ‘미루는’ 코드입니다. defer를 사용하여 코드를 작성하게 되면 defer의 코드블록 안에 작성한 코드는 코드블록에서 나가기 바로 직전에 실행됩니다. 역시 코드로 보는 것이 이해가 빠르겠네요! 예시는 Swift 문서에서 가져온 예시입니다.

func f() {
    defer { print("First defer") }
    defer { print("Second defer") }
    print("End of function")
}
f()
// Prints "End of function"
// Prints "Second defer"
// Prints "First defer"

이렇게 defer 코드블록에 작성한 코드는 마지막에 실행되는 것을 볼 수 있습니다. 그리고 이미 예시에서도 나와있지만 코드블록 안에 defer를 여러 번 사용할 수 있는데요, 그럴 때에는 마지막에 작성된 defer부터 역순으로 실행되게 됩니다.

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

test()

/*
2
4
5
3
1
*/

이런식으로 직접 예제를 만들어본다면 바로 이해하실 수 있을 것 같습니다.


그러면 defer는 언제 쓰면 좋을까요?

먼저 야곰의 Swift Programming에 좋은 예시가 있어서 가져와보았습니다. defer는 아래와 같은 상황을 해결해주기도 합니다.

func writeDataO {
    let file = openFileO
    
    if ... {
        closeFile(file)
        return
    }
    
    if ... {
        closeFile(file)
        return
    }
    
    doseFile(file) // 처리 끝
}
  • 함수에서 파일을 열고 작업 후 파일을 닫아주어야 할 때, 이렇게 중복되는 코드를 곳곳에 작성하게 되면 구조상 불편하기도 하고, 호출을 놓치는 경우도 발생할 수 있습니다.
  • 이때 defer로 간결하게 해결해줄 수 있습니다.
func writeData() {
       let file = openFileO
    // 함수 코드 볼록을 빠져나가기 직전 무조건 실행되어 파일을 일지 않고 닫아줍니다.
    
       defer {
           closeFile(file)
       }
    
       if ... {
           return
       }
    
       if ... {
           return
       }
    // 처리 끝
}


error throw

defer는 일반적인 함수, 메서드, 조건문 등 코드 블록이라면 어디서든 사용할 수 있지만 특히 에러 처리 상황에서 유용하다고 합니다.

func errorThrowTest(error: Bool) throws {
    defer {
        print("1")
    }
    
    if error {
        enum SomeError: Error {
            case unowned
        }
        
        print("error!")
        throw SomeError.unowned
    }
    
    defer {
        print("2")
    }
    
    defer {
        print("3")
    }
}

try? errorThrowTest(error: true)
/* 출력
 error!
 1
 */

try? errorThrowTest(error: false)
/* 출력
 3
 2
 1
 */
  • 에러가 발생한 경우 error가 throw 되기 전에 실행돤 defer문만 동작하게 됩니다.
  • 그리고 defer문을 작성할 때에는 break나 return등과 같이 구문을 빠져나갈 수 있는 코드 또는 오류를 던지는 코드를 작성하면 안됩니다.


guard

guard문을 탈출하는 상황에서도 error를 throw하는 상황과 똑같이 동작합니다.

func test(num: Int) {
    defer {
        print("1")
    }
    
    guard num == 0 else {
        print("0이 아닙니다.")
        return
    }
    
    defer {
        print("2")
    }
    
    print("3")
}

test(num: 0)
/*
 3
 2
 1
 */

test(num: 1)
/*
 0이 아닙니다.
 1
 */


return과 defer, 무엇이 먼저일까?

여기서부터는 How “defer” operator in Swift의 내용을 참고하여 작성합니다.

return과 defer 중에서 무엇이 먼저 실행될까요?

var a = "Hello"

func b() -> String {
    defer { a.append(" world") }
    return a
}

func d() -> String {
    a.append(" world")
    return a
}

먼저 defer가 return보다 먼저 실행된다고 가정한다면 b() 함수와 d() 함수는 같다고 할 수 있을 것입니다. 그런데 이 코드를 돌려보면..

a = "Hello"
print(b()) // Hello
a = "Hello"
print(d()) // Hello world

defer를 사용한 b() 함수에서는 defer가 실행되지 않음을 확인할 수 있습니다. defer는 return보다 늦게 실행되는 것입니다.

이에 대해서 원문에서는 Hopper Disassembler 을 사용하여 defer가 어떻게 동작하는지를 확인하고 있는데 아직은 이 포스팅에 담을만큼 잘 이해하지를 못하겠네요. ㅜ 저는 좀 더 내공이 쌓인 후 다시 보는 것이 좋을 것 같습니다!



참고