프로그래밍iOS/모바일 개발자

스위프트에서 클로저(익명 함수)는 어떻게 작동하며, 함수와 무엇이 다른지, 사용 시 메모리 관리와 관련된 측면은 무엇인지 설명하세요.

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

답변.

질문의 배경:

클로저는 함수형 언어에서 기원한 개념으로, 코드 블록을 값으로 전달할 수 있게 해줍니다. 스위프트에서는 클로저(다른 언어의 람다와 유사)는 처음부터 존재했습니다. 이를 통해 비동기 호출을 우아하게 구현하고, 액션을 위임하며, 컬렉션을 정렬하고 필터링하는 등의 작업을 수행할 수 있습니다.

문제:

클로저는 주변 컨텍스트에서 변수를 캡처합니다. 이러한 변수 중에 클래스의 객체에 대한 참조가 있을 경우, 특히 클로저가 이 클래스의 속성으로 저장되어 있을 때 retain cycle이 발생할 수 있습니다. 또한 escaping과 non-escaping 클로저의 차이를 이해하고 값을 캡처하는 방식이 어떻게 작동하는지를 인식하는 것이 중요합니다.

해결책:

스위프트는 클로저를 독립적인 객체로 구현하였으며, 이들은 컨텍스트를 캡처할 수 있습니다. 클로저의 소유자는 아키텍처에 주의해야 합니다.

코드 예시:

class Performer { var onComplete: (() -> Void)? func doWork() { onComplete = { print("작업 완료!") } } } // 클로저 내부에서 self 캡처 class Test { var value = 0 func start() { DispatchQueue.global().async { [weak self] in self?.value = 1 } } }

주요 특징:

  • 클로저는 참조 또는 값으로 캡처할 수 있습니다.
  • escaping/non-escaping 클로저는 클로저의 생애 주기에 영향을 미칩니다.
  • 저장된 속성으로서의 클로저는 쉽게 retain cycle을 생성합니다.

함정 질문들.

클로저가 self를 명시적으로 캡처하지 않을 경우, retain cycle이 발생할 수 있나요?

네, 클로저가 클래스의 strong property로 저장되고, 클로저 내부에서 self에 접근할 경우, 명시적으로 self를 쓰지 않아도 retain cycle이 발생합니다. 가능하면 [weak self]/[unowned self]를 사용하거나 클로저를 로컬로 만드는 것이 좋습니다.

escaping 클로저와 non-escaping 클로저의 차이는 무엇인가요?

Escaping 클로저는 함수 실행이 끝난 후 호출될 수 있으며, 일반적으로 비동기 작업에 사용됩니다. Non-escaping 클로저는 함수가 실행되는 동안 호출됩니다. Escaping 클로저의 self 캡처 순서는 다릅니다.

코드 예시:

func asyncWork(completion: @escaping () -> Void) { DispatchQueue.global().async { completion() } }

클로저가 캡처한 변수의 값을 변경할 수 있나요?

네, 클로저가 var로 선언된 변수를 캡처한 경우, 그 값을 변경할 수 있습니다(값 타입의 경우). 클래스의 경우 항상 참조가 되므로 속성을 변경할 수 있습니다.

코드 예시:

var value = 1 let closure = { value += 1 } closure() print(value) // 2

일반적인 오류 및 안티 패턴

  • 클래스의 속성으로 클로저를 설정하고 [weak self] 없이 self에 접근하기.
  • 클로저를 너무 크게 선언하여 이해 및 디버깅이 어렵게 만들기.
  • non-escaping 대신 escaping을 사용하기.

실생활 예시

부정적인 케이스

ViewController가 클로저를 속성으로 저장하고, 내부에서 self에 직접 접근할 때 발생합니다. 그 결과 retain cycle이 발생합니다: ViewController => 클로저 => ViewController. 화면 전환이 이루어져도 메모리가 해제되지 않습니다.

장점: 코드가 간결하고 짧아 보입니다.

단점: 메모리 누수, ViewController가 메모리에 "고착됨", 잠재적인 버그 발생 가능성.

긍정적인 케이스

클로저 내부에서 [weak self] 또는 [unowned self]를 사용하거나 클로저가 객체의 생애 주기보다 오래 유지되지 않도록 합니다. 코드 리뷰에서 이러한 부분을 검토합니다.

장점: 자원 해제가 올바르며, 예기치 않은 메모리 누수가 없습니다.

단점: [weak self]는 해제 시 주의가 필요하며, 잘못된 사용 시 암묵적인 크래시가 발생할 수 있습니다.