질문의 역사:
Objective-C의 발전과 Swift의 등장으로 Apple에서는 프로그래머가 retain/release 명령을 명시적으로 다루지 않고도 응용 프로그램의 메모리를 자동으로 안전하게 관리하는 방법에 관한 문제가 제기되었습니다. Swift는 클래스 객체에 대한 참조 수를 추적하고 마지막 소유자가 사라지면 자동으로 메모리를 해제하는 Automatic Reference Counting (ARC)을 사용합니다.
문제:
Swift는 다중 패러다임 언어로서 값 타입(struct, enum)과 참조 타입(class)을 모두 사용합니다. 후자의 경우, 메모리는 적시에 해제되어야 하며 그렇지 않으면 메모리 누수가 발생할 수 있습니다. 복잡한 상황은 retain cycle(순환 참조)으로, 두 객체가 서로를 참조하여 ARC가 메모리를 해제하지 못하게 합니다.
해결 방법:
Swift는 클래스 객체에 대해서만 ARC를 적용합니다. 참조 수가 0이 되면 메모리가 해제됩니다. retain cycle 문제를 해결하기 위해 weak/unowned 키워드를 사용합니다.
코드 예:
class Person { var name: String var pet: Pet? init(name: String) { self.name = name } deinit { print("\(name) is being deinitialized") } } class Pet { var owner: Person? init() {} deinit { print("Pet is being deinitialized") } } var tom: Person? = Person(name: "Tom") var cat: Pet? = Pet() tom!.pet = cat cat!.owner = tom tom = nil cat = nil // 두 객체가 해제되지 않음, retain cycle 발생
주요 특징:
값 타입에 ARC를 적용할 수 있나요?
아니요, ARC는 클래스 객체의 참조만 추적합니다. 구조체와 enum은 값으로 전달되며, Swift는 일반적으로 범위를 벗어날 때 자동으로 메모리를 해제합니다.
서로 상호 참조하는 두 객체의 속성에서 weak/unowned를 사용하지 않으면 어떻게 되나요?
순환 참조(retain cycle)가 발생하여 ARC는 이러한 객체의 메모리를 해제하지 않습니다. 이런 코드는 메모리 누수를 초래하며, 상관관계의 한 쪽은 최소한 weak (또는 unowned)를 사용하는 것이 좋습니다.
weak가 있는 경우 unowned는 왜 필요합니까?
unowned는 다른 객체의 생애 동안 반드시 존재해야 하며 결코 nil이 되어서는 안 되는 경우에 필요합니다. weak는 nil을 허용하는 링크이고, unowned는 nil을 허용하지 않으나 retain 카운터를 증가시키지 않습니다.
코드 예:
class A { var b: B? } class B { unowned var a: A // B의 생애 동안 결코 nil이 아님 }
두 컨트롤러가 서로 강한 참조를 포함하고 있습니다: 하나는 delegate 속성을 통해, 다른 하나는 자식 컨트롤러 배열을 통해. 생성자는 delegate에서 weak를 사용하지 않습니다.
장점: 오류가 즉시 보이지 않고 처음에는 모든 것이 "작동"하는 것처럼 보입니다.
단점: 애플리케이션의 양이 증가함에 따라 메모리가 해제되지 않으며, 메모리 누수가 발생하고 제한된 메모리 사용 시 크래시가 발생할 수 있습니다.
delegate와 이벤트 수신자가 weak로 설정되며, 컨트롤러 배열은 컨트롤러가 사라질 때마다 정리됩니다. 모든 것이 적절하게 문서화되어 있습니다.
장점: 누수가 없고, 모든 객체가 제때 해제되며 성능 저하가 없습니다.
단점: 어딘가에서 강한 참조가 잊어버려지면 delegate가 예기치 않게 소멸할 수 있으며, 애플리케이션 아키텍처에 주의가 필요합니다.