본문 바로가기
💻 Programming 개발/🍎 iOS 개발, Swift

defer가 호출되는 순서 / defer가 호출되지 않는 경우

by 킴디 kimdee 2023. 2. 15.
반응형

 

defer 구문은, defer 구문이 있는 곳에서, 프로그램 컨트롤을 스코프 바깥으로 옮기기 바로 직전에 실행하려는 코드에 사용한다.

defer {
    // statements
}

defer  문 안에 있는 내용은 프로그램 제어가 어떻게 이동되는지와는 관계 없이 실행된다. 예를 들어 수동적으로 자원관리가 들어갈 때, 에러가 발생되더라도 액션을 수행해야 할 때 사용한다.

Defer가 실행되는 순서

여러개의 defer문이 있을 때

같은 스코프 내에 여러개의 defer 문이 있으면, 실행되는 순서는 보이는 순서의 역순. 마지막 defer 문을 첫번째로 실행한다는 의미는, 마지막 defer 문 안의 내용은 다른 defer 문에 의해서 정리되는 리소스를 참조할 수 있다는 뜻.

func 디퍼함수() {
    defer { print("첫번째") }
    defer { print("두번째") }
    print("마지막")
}
디퍼함수()
// 마지막
// 두번째
// 첫번째

defer문이 중첩되어 있을 때

중첩되어 있을 경우도 가장 바깥쪽에 있는 defer부터 실행된다.

func 중첩디퍼함수() {
    defer {
        defer {
            defer {
                print("중첩 첫번째")
            }
            print("중첩 두번째")
        }
        print("중첩 마지막")
    }
}

중첩디퍼함수()
// 중첩 마지막
// 중첩 두번째
// 중첩 첫번째

일반적인 용례

컨텍스트를 열거나 닫을 때

가장 일반적으로 사용하는 케이스는 스코프 안에서 컨텍스트를 열거나 닫을 때. 예를 들어서 파일 접근에 대해 다룰 경우.  FileHandle 은 한 번 접근이 끝나면 반드시 닫아줘야하는데 이때 defer 구문을 이용해 잊지 않고 닫아줄 수 있다.

func writeFile() {
    let file: FileHandle? = FileHandle(forReadingAtPath: filepath)
    defer { file?.closeFile() }

    // 파일관련된 코드들 등등 
}

결과값

컴플리션 콜백에서 반환되는 결과값 관련하여 사용.

func getData(completion: (_ result: Result<String>) -> Void) {
    var result: Result<String>?

    defer {
        guard let result = result else {
            fatalError("We should always end with a result")
        }
        completion(result)
    }

    // 결과값 생성하기 
}

이렇게 해서 결과값을 항상 검증하고 컴플리션 핸들러를 실행하게 함. 결과값이 nil 일 경우, fatalError 가 던져지고 앱이 fail됨

로딩 인디케이터

func performLogin() {
    shouldShowProgressView = true

    defer {
        shouldShowProgressView = false
    }

    do {
        let _ = try await LoginManager.performLogin()

        DispatchQueue.main.async {
            self.coordinator?.successfulLogin()
        }

    } catch {
        let error = error
        showErrorMessage = true

defer가 실행되지 않는 상황

defer문이 읽히기 전에 종료가 되는 상황일 때

import UIKit

var greeting = "Hello, playground"

func deferSometimeRun(value: Int?) {
    guard let value = value else {
        print("nil이라서 종료")
        return
    }
    
    defer {
        print("#DEFER: 값이 \\(value) 일 때 실행됐어요.")
    }
    
    print("값 : \\(value)")
}

deferSometimeRun(value: nil)
deferSometimeRun(value: 6)

// nil이라서 종료
// 값 : 6
// #DEFER: 값이 6 일 때 실행됐어요.
func deferAlwaysRun(value: Int?) {
    defer {
        print("#DEFER: 값이 \\(value) 일 때 실행됐어요.")
    }
    
    guard let value = value else {
        print("nil이라서 종료")
        return
    }

    print("값 : \\(value)")
}

deferAlwaysRun(value: nil)
deferAlwaysRun(value: 7)

// nil이라서 종료
// #DEFER: 값이 nil 일 때 실행됐어요.
// 값 : 7
// #DEFER: 값이 Optional(7) 일 때 실행됐어요.

때문에 defer 구문은 항상 해당 스코프 가장 윗쪽에 해두는 것을 권장. (왜냐면 defer문 쓰는 것 자체가 종료를 지연하고 마지막으로 실행하는 것이기 때문에 )


참고링크

Statements - The Swift Programming Language (Swift 5.7)

A complete guide to the Swift defer statement - LogRocket Blog

Defer usage in Swift

 

 

 


 

 

오늘도 읽어주셔서 감사합니다. 

 

궁금하거나 나누고 싶은 얘기가 있으시면 댓글로 알려주세요!

재밌게 읽으셨다면 공감과 구독은 큰 힘이 됩니다. 

 

항상 감사합니다.

 

이 글은 doy.oopy.io 에도 발행되어 있습니다. 

 

 

반응형

댓글