Swift defer로 스코프 종료 시 정리 작업 실행하기
Swift Defer. Clean up before you leave.
핵심 요약
- Swift의 defer 블록은 선언된 위치와 관계없이 해당 블록이 속한 스코프가 끝나기 직전에 실행되며, 중괄호로 구분되는 함수나 반복문의 범위에 적용된다.
- 임시 파일을 만든 직후 삭제 작업을 defer로 등록하면 함수가 정상적으로 끝나거나 guard의 return을 만나도 스코프 종료 시 정리 코드가 실행된다.
- HealthKit 관찰 루프에서는 각 반복의 처리가 끝난 뒤 completion handler를 호출하도록 defer를 둘 수 있으며, 임시 자원·상태 변경 등의 정리에도 같은 방식을 쓸 수 있다.
도입
Swift의 defer는 선언한 코드 위치에서 즉시 실행되지 않고, 해당 코드가 속한 스코프가 끝날 때 실행된다. 스코프는 함수뿐 아니라 for 루프 등 중괄호로 둘러싸인 범위를 뜻한다. 실행 시점을 놓치기 쉬운 특성 때문에 매일 사용하는 기능은 아니지만, 자원 생성이나 작업 완료 후 정리 코드를 빠뜨리지 않도록 설정 코드 가까이에 둘 수 있다.
상세 요약
실행 시점은 현재 스코프의 끝
Swift의 defer는 블록을 선언한 위치에서 실행하지 않는다. 블록이 속한 스코프가 끝나기 직전에 실행한다. 여기서 스코프는 가장 가까운 중괄호 쌍으로 구분되는 범위다. 함수, for 루프 등이 각각 스코프를 만든다.
func foo() {
defer { print("end parent scope") }
for i in 0...10 {
defer { print("end scope, \(i)") }
print(i)
}
}
루프 안의 defer는 각 반복의 스코프가 끝날 때 실행된다. 함수 스코프에 둔 defer는 루프가 끝나고 함수 스코프가 종료되기 직전에 실행된다. 따라서 defer의 위치만 보는 대신 어느 중괄호 범위에 들어 있는지 확인해야 한다.
임시 파일 정리
예시의 fetch 함수는 응답 데이터를 임시 디렉터리의 파일에 기록한 뒤 XMLParser로 처리한다. 기존 코드는 파싱 이후 파일을 삭제한다. 그러나 XMLParser(contentsOf:)가 nil을 반환하면 guard의 return으로 함수가 끝나므로, 뒤에 둔 삭제 코드에는 도달하지 않는다.
try data.write(to: url)
defer {
try? FileManager.default.removeItem(at: url)
}
guard let parser = XMLParser(contentsOf: url) else {
return
}
parser.parse()
파일 기록 직후 삭제 작업을 등록하면, 파서 생성이 실패해 함수가 반환되는 경우에도 함수 스코프가 끝나면서 정리 블록이 실행된다. 예시는 삭제 호출에 try?를 사용한다. 정리 코드를 자원 생성 지점 가까이에 배치하면 처리 경로마다 삭제 코드를 반복해서 쓰지 않아도 된다.
처리 완료 후 핸들러 호출
HealthKit 관찰 예시에서는 for await 루프의 각 항목을 처리한 뒤 handler()를 호출해야 한다. 루프 본문 시작 부분에 defer { handler() }를 두면 현재 반복 스코프가 종료될 때 핸들러가 실행된다. 심박수 데이터가 포함된 경우 processHeartRate()를 호출하며, 이 처리가 끝난 뒤 핸들러가 불린다. 관련해 Swift에서 AsyncSequence로 데이터를 단계적으로 불러오기에서는 AsyncSequence를 이용한 데이터 로딩 흐름을 다룬다.
적용 기준
defer는 매일 필요한 기능은 아니지만, 생성한 임시 자원의 정리, 획득한 lock의 해제, 변경한 상태의 복구, 나중에 호출해야 하는 completion handler 처리에 사용할 수 있다. 공통점은 어떤 설정이나 처리를 시작한 뒤 반드시 뒤따라야 하는 작업이 있다는 점이다. 해당 작업을 시작 지점 가까이에 등록하고, 현재 스코프의 끝에서 실행되도록 구성한다.
결론
defer의 핵심은 정리 작업을 자원 생성이나 처리 시작 지점 가까이에 등록하고, 실제 실행은 스코프가 끝날 때 맡기는 것이다. 임시 파일 삭제, HealthKit 관찰 처리 후 completion handler 호출처럼 반드시 뒤따라야 하는 작업을 한곳에 표현할 수 있다. 다만 defer는 현재 스코프 종료 시 실행되므로, 블록을 어느 중괄호 범위 안에 두는지가 실행 시점을 결정한다.