defer 연산자는 스위프트에서 자원을 안전하고 보장된 방식으로 정리하거나 범위에서 나갈 때 코드를 실행하기 위해 도입되었습니다. 이는 다른 언어의 finally/using/RAII와 유사합니다.
다단계 초기화, 파일 작업, 함수에서의 교차 시나리오들은 자원 해제가 보장되거나 로직을 실행할 필요가 있습니다 (파일 닫기, mutex 잠금 해제, 객체를 풀로 반환, 임시 상태 초기화). defer가 등장하기 전에는 모든 return에서 수동으로 작업해야 했습니다.
defer는 현재 범위를 벗어날 때 그 코드가 실행되도록 보장해줍니다. 정상적인 exit이든 throw를 통한 것이든 상관없이 작동합니다. 여러 개의 defer를 선언할 수 있으며, 이는 선언된 순서의 역순으로 실행됩니다.
자원 해제 예시:
func processFile(path: String) throws { let file = try openFile(path) defer { file.close() } // 오류가 발생해도 보장된 호출 // ... file 작업 ... }
주요 특징들:
defer가 참조로 변수를 캡처할 수 있나요? 이로 인해 클로저의 동작에 어떤 영향이 있나요?
네, defer는 호출 시 사용된 모든 변수를 캡처합니다. 클로저의 캡처 규칙에 따라 (값 타입은 복사, 참조 타입은 참조). 변경 가능한 외부 값 변수를 캡처하는 것은 오류를 발생시킬 수 있으며, defer 실행 시점에 변경될 수 있습니다.
주기 및 함수 내부에 있는 중첩된 defer는 어떻게 작동하나요?
if defer가 반복문 내부에 선언되면, 각 반복 범위 완료 시마다 실행됩니다:
for _ in 1...3 { defer { print("Iteration 끝") } print("내부") }
출력:
내부
Iteration 끝
내부
Iteration 끝
내부
Iteration 끝
defer가 실행되지 않을 수 있나요?
defer 코드의 실행은 범위를 실제로 벗어날 경우에만 보장됩니다. 그러나 프로세스가 비정상적으로 종료되면 (fatalError, 크래시)를 사용하거나 exit가 호출되면 defer는 실행되지 않습니다. 또한 defer는 객체 메서드 수준에서는 작동하지 않으며 오직 함수/블록의 범위에서만 작동합니다.
파일을 연 후 작업하는 코드에서 다양한 return/throw로 반환하였고, 한 곳에서 파일을 닫는 것을 잊었습니다. 결국 열려있는 파일 핸들이 시스템에 누수되었습니다.
장점:
단점:
보장된 mutex 잠금 해제, 파일 디스크립터 해제 및 진행 상태 플래그 초기화를 위한 defer 사용:
func criticalSection() { lock() defer { unlock() } // ... 작업 ... }
장점:
단점: