프로그래밍Middle/Senior iOS 개발자

스위프트에서 defer 연산자의 본질은 무엇이며, 주된 사용 시나리오와 클로저 및 자원 관리와의 관계는 무엇인가요?

Hintsage AI 어시스턴트로 면접 통과

답변.

질문의 배경

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 작업 ... }

주요 특징들:

  • 범위를 벗어나는 모든 경우에서 실행됩니다 (return, throw, break),
  • 여러 defer를 연속으로 선언할 수 있으며, 역순으로 실행됩니다,
  • 자원 관리, 오류 처리, 잠금/잠금 해제, 임시 상태 초기화에 매우 유용합니다.

함정 질문.

defer가 참조로 변수를 캡처할 수 있나요? 이로 인해 클로저의 동작에 어떤 영향이 있나요?

네, defer는 호출 시 사용된 모든 변수를 캡처합니다. 클로저의 캡처 규칙에 따라 (값 타입은 복사, 참조 타입은 참조). 변경 가능한 외부 값 변수를 캡처하는 것은 오류를 발생시킬 수 있으며, defer 실행 시점에 변경될 수 있습니다.

주기 및 함수 내부에 있는 중첩된 defer는 어떻게 작동하나요?

if defer가 반복문 내부에 선언되면, 각 반복 범위 완료 시마다 실행됩니다:

for _ in 1...3 { defer { print("Iteration 끝") } print("내부") }

출력:

내부
Iteration 끝
내부
Iteration 끝
내부
Iteration 끝

defer가 실행되지 않을 수 있나요?

defer 코드의 실행은 범위를 실제로 벗어날 경우에만 보장됩니다. 그러나 프로세스가 비정상적으로 종료되면 (fatalError, 크래시)를 사용하거나 exit가 호출되면 defer는 실행되지 않습니다. 또한 defer는 객체 메서드 수준에서는 작동하지 않으며 오직 함수/블록의 범위에서만 작동합니다.

일반적인 오류 및 안티 패턴

  • 비동기 작업에 대해 defer를 사용하기 (마지막 defer가 해제를 수행하고 비동기 코드는 아직 완료되지 않음),
  • 외부 변수를 묵시적으로 캡처하기 (다중 스레드에서의 경쟁 조건),
  • 많은 defer를 연속해서 선언하기 — 가독성과 디버깅이 떨어짐.

실제 사례

부정적인 사례

파일을 연 후 작업하는 코드에서 다양한 return/throw로 반환하였고, 한 곳에서 파일을 닫는 것을 잊었습니다. 결국 열려있는 파일 핸들이 시스템에 누수되었습니다.

장점:

  • 단순한 선형 로직

단점:

  • 각 종료에서 자원을 해제하는 것을 잊기 쉬움
  • 메모리 누수/자원 누수

긍정적인 사례

보장된 mutex 잠금 해제, 파일 디스크립터 해제 및 진행 상태 플래그 초기화를 위한 defer 사용:

func criticalSection() { lock() defer { unlock() } // ... 작업 ... }

장점:

  • 자원의 높은 안전성
  • 오류 수 감소

단점:

  • 많은 defer가 있을 경우 추가적인 범위 중첩 발생