🔥 강한 참조 순환 이해하기
앞선 예제들에서 ARC는 새로 생성된 Person
인스턴스를 참조하는 강한 참조의 수를 추적하여, 더 이상 필요하지 않을 때 해당 인스턴스를 할당 해제할 수 있었어요.
하지만 때로는 클래스 인스턴스가 서로를 강하게 참조하고 있어서, 강한 참조 수가 절대 0이 되지 않는 경우가 있답니다. 이를 강한 참조 순환(strong reference cycle)이라고 해요.
강한 참조 순환은 클래스 인스턴스 간의 관계를 약한 참조(weak reference)나 소유되지 않은 참조(unowned reference)로 정의하면 해결할 수 있지요. 이에 대해서는 클래스 인스턴스 간 강한 참조 순환 해결하기에서 자세히 다룰 거예요. 하지만 그 전에, 강한 참조 순환이 어떻게 발생하는지 이해하는 것이 중요하답니다.
아파트 단지와 거주자를 모델링한 Person
과 Apartment
클래스를 정의한 다음 예제 코드를 통해, 의도치 않게 강한 참조 순환이 발생하는 상황을 살펴볼게요:
class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { print("\(name) is being deinitialized") } } class Apartment { let unit: String init(unit: String) { self.unit = unit } var tenant: Person? deinit { print("Apartment \(unit) is being deinitialized") } }
swift
코드를 하나씩 분석해 볼까요?
- 모든
Person
인스턴스는String
타입의name
속성과 초기값이nil
인 옵셔널apartment
속성을 가져요.apartment
속성이 옵셔널인 이유는 사람이 항상 아파트를 갖고 있지는 않기 때문이에요.
- 마찬가지로 모든
Apartment
인스턴스는String
타입의unit
속성과 초기값이nil
인 옵셔널tenant
속성을 가지고 있죠.tenant
속성이 옵셔널인 이유는 아파트가 항상 거주자를 갖고 있지는 않기 때문이랍니다.
- 두 클래스는 deinitializer를 정의하고 있는데, 이는 해당 클래스의 인스턴스가 할당 해제될 때 이를 출력하도록 해요.
- 이를 통해
Person
과Apartment
인스턴스가 기대한 대로 할당 해제되는지 확인할 수 있겠죠?
- 이를 통해
이제 아래 코드 조각에서는 john
과 unit4A
라는 옵셔널 타입의 변수를 정의하고 있는데, 이는 아래에서 특정 Apartment
와 Person
인스턴스로 설정될 거예요. 두 변수는 옵셔널이기에 초기값이 nil
이에요:
var john: Person? var unit4A: Apartment?
swift
이제 특정 Person
인스턴스와 Apartment
인스턴스를 생성하고, 이를 john
과 unit4A
변수에 할당할 수 있어요:
john = Person(name: "John Appleseed") unit4A = Apartment(unit: "4A")
swift
이 두 인스턴스를 생성하고 할당한 후의 강한 참조 상황은 다음과 같아요:
john
변수는 새Person
인스턴스에 대한 강한 참조를 갖고 있죠.unit4A
변수는 새Apartment
인스턴스에 대한 강한 참조를 갖고 있답니다.
이제 두 인스턴스를 연결하여 사람은 아파트를 갖고, 아파트는 거주자를 갖도록 할 수 있어요. 여기서 느낌표(!
)는 john
과 unit4A
옵셔널 변수에 저장된 인스턴스를 강제 추출(unwrap)하여 해당 인스턴스의 속성을 설정할 수 있도록 해줍니다:
john!.apartment = unit4A unit4A!.tenant = john
swift
두 인스턴스를 서로 연결한 후의 강한 참조 상황은 다음과 같네요:
안타깝게도, 이렇게 두 인스턴스를 연결하면 이들 간에 강한 참조 순환이 발생하게 됩니다:
Person
인스턴스는 이제Apartment
인스턴스에 대한 강한 참조를 갖게 되죠.Apartment
인스턴스는Person
인스턴스에 대한 강한 참조를 갖게 된답니다.
따라서 john
과 unit4A
변수가 갖고 있던 강한 참조를 끊더라도, 참조 수가 0이 되지 않아 ARC에 의해 인스턴스들이 할당 해제되지 않아요:
john = nil unit4A = nil
swift
이 두 변수를 nil
로 설정했을 때 어떤 deinitializer도 호출되지 않은 점에 주목해 보세요. 강한 참조 순환으로 인해 Person
과 Apartment
인스턴스는 할당 해제되지 않고, 앱에서 메모리 누수(memory leak)를 야기하게 됩니다.
john
과 unit4A
변수를 nil
로 설정한 후의 강한 참조 상황은 다음과 같아요:
Person
인스턴스와 Apartment
인스턴스 간의 강한 참조는 여전히 남아있고, 이는 끊을 수 없게 되는 거죠.
이렇게 강한 참조 순환은 메모리 관리에서 주의해야 할 중요한 개념 중 하나랍니다. 다행히도 Swift는 약한 참조와 소유되지 않은 참조라는 개념을 통해 이를 해결할 수 있는 방법을 제공하고 있어요. 다음 장에서는 이에 대해 자세히 알아보도록 할게요!