[메모리 누수(Memory Leak)]
메모리 누수(Memory Leak)는 프로그램이 필요하지 않은 메모리를 계속해서 점유하고 있음을 의미해요.
계속 점유한다는 게 뭐가 문제냐!라고 생각할 수 있는데 크게 3가지 문제가 발생합니다.
📌 성능 저하
필요하지 않은 메모리를 점유하면 즉, 메모리 누수가 발생하면 available 한 메모리 공간이 점점 줄어듭니다. 메모리가 충분히 크다면 괜찮을지는 몰라도 메모리가 제한적인 환경에서는 비정상적인 문제가 발생할 수 있습니다.
📌 자원 낭비
쓰지도 않는 메모리를 점유한다면 당연히 자원을 낭비하게 되겠죠?!
📌 안정성 저하
메모리 누수가 심각해지면 시스템이 비정상적으로 종료될 수 있으므로 최선의 사용자 경험을 제공할 수 없습니다.
따라서... 필요하지 않은 메모리를 적재적소에 해제하여 이러한 문제를 예방하는 것이 중요합니다.
Swift에서는 ARC(Automatic Reference Counting) 메모리 관리 기법을 통해 자동으로 메모리를 관리해 주지만 그럼에도 불구하고 메모리 누수가 발생할 수 있죠.
[순환 참조(Retain Cycle)]
가장 대표적인 예시가 순환 참조입니다.
순환 참조(Retain Cycle)는 두 개 이상의 클래스 인스턴스가 서로를 강하게 참조하고 있어서 참조 카운트가 0이 되지 않는 상황을 말합니다.
ARC는 클래스의 새로운 인스턴스를 만들 때마다 메모리를 할당하고, 인스턴스가 더 이상 필요하지 않게 되면 메모리를 해제합니다.
참조 카운트란 특정 인스턴스를 참조하고 있는 다른 인스턴스나 변수, 상수의 수를 의미하는데 이는 인스턴스가 더 이상 필요하지 않은지 판단하는 기준이 됩니다.
그러니까 참조 카운트가 0이 되면 '해당 인스턴스는 더 이상 필요하지 않구나!'라고 판단하고 메모리에서 해제합니다.
[참조 카운트 증가와 감소]
예시를 통해 참조 카운트 증가/감소를 살펴보면,
class MyClass {
var name: String
init(name: String) {
self.name = name
}
deinit {
print("MyClass 인스턴스가 메모리에서 해제되었습니다.")
}
}
var instance1: MyClass? = MyClass(name: "Example") // MyClass 인스턴스 생성, 참조 카운트 1
var instance2: MyClass? = instance1 // instance1을 instance2가 참조, 참조 카운트 2
var instance3: MyClass? = instance1 // instance1을 instance3이 참조, 참조 카운트 3
instance2 = nil // instance2가 instance1을 더 이상 참조하지 않음, 참조 카운트 2
instance3 = nil // instance3이 instance1을 더 이상 참조하지 않음, 참조 카운트 1
instance1 = nil // instance1이 MyClass 인스턴스를 더 이상 참조하지 않음, 참조 카운트 0
MyClass의 인스턴스인 instance1에 대해서 참조를 할 때마다 참조 카운트는 1씩 증가하고, 참조를 끝 낼 때마다 참조 카운트는 1씩 감소함을 알 수 있습니다.
예시와 같은 참조를 강한 참조(Strong Reference)라고 하는데요. 강한 참조는 인스턴스의 참조 카운트를 증가시키기 때문에 이로 인해 참조하는 인스턴스는 메모리에서 해제되지 않습니다 😱
그렇다면, 참조를 하면서 인스턴스의 참조 카운트에 관여하지 않는 방법은 뭐가 있을까요?
바로 약한 참조(Weak Reference)와 미소유 참조(Unowned Reference)가 있습니다.
[약한 참조 (Weak Reference) ]
약한 참조(Weak Reference)는 참조 카운트를 증가시키지 않기 때문에 인스턴스가 해제되면 자동으로 nil이 됩니다. 그렇기 때문에 항상 옵셔널 타입이어야 합니다.
class MyClass {
var name: String
weak var weakReference: MyClass?
init(name: String) {
self.name = name
}
}
var instance: MyClass? = MyClass(name: "Example")
instance?.weakReference = instance
instance = nil
instance가 nil이 되면 약한 참조를 하고 있는 weakReference는 자동으로 nil이 됩니다.
[미소유 참조(Unowned Reference)]
미소유 참조(Unowned Reference) 역시 참조 카운트를 증가시키지 않지만.. 약한 참조와 달리 항상 값이 있다고 생각해야 합니다. 그렇기 때문에 옵셔널 타입이 아닙니다.
class MyClass1 {
var name: String
var reference: MyClass2?
init(name: String) {
self.name = name
}
}
class MyClass2 {
var name: String
unowned var unownedReference: MyClass1
init(name: String, unownedReference: MyClass1) {
self.name = name
self.unownedReference = unownedReference
}
}
var instance1: MyClass1? = MyClass1(name: "Example1")
var instance2: MyClass2? = MyClass2(name: "Example2", unownedReference: instance1!)
instance1?.reference = instance2
instance1 = nil
instance2 = nil
instance1이 nil이 되어도 unownedReference는 nil이 되지 않습니다.
참조하는 인스턴스가 해제되더라도 nil이 되지 않기 때문에 조심해야 합니다.
인스턴스가 메모리에서 해제된 후 다시 접근하려고 하면 런타임 에러가 발생하기 때문입니다.
이를 피하기 위해서, 미소유 참조보다는 약한 참조를 사용하는 것이 안전하긴 합니다..!
'iOS' 카테고리의 다른 글
[SwiftUI] CMPedometer 클래스로 만보기 만들어보기 (0) | 2023.10.18 |
---|