## Swift에서는 메모리 관리를 어떻게 할까?
-
기초적인 대부분의 메모리관리는 ARC가 알아서 합니다.
-
기본적으로 관리되는 메모리의 관계는 클래스의 객체를 가리키는 각각의 reference(참조)는 '강한참조'입니다. 최소한 하나의 '강한참조'가 존재하는한 메모리는 해제되지 않습니다.
-
이 최소한 하나의 '참조'가 강한참조가 아니면 메모리에서 해제됩니다.
-
객체 생성시 참조와 객체의 관계 다이어그램
- 마지막의 코드처럼 nil을 붙이게되면 deinit()이 호출됩니다. ( 메모리 해제시 호출됩니다.)
Retain Cycle이란?
-
대부분의 경우 ARC가 작동해서 참조를 해제하지만 작동하지 않는 경우가 있습니다.
class TestClass{ var testClass: TestClass? = nil init(){ print("init") } deinit{ print("deinit") } } var testClass1: TestClass? = TestClass() var testClass2: TestClass? = TestClass() testClass1?.testClass = testClass2 testClass2?.testClass = testClass1 testClass1 = nil testClass2 = nil
이렇게 만들면 클래스 1과 클래스 2는 서로를 참조하고 있습니다. 1 = 2, 2 = 1
-
아까 참조의 예시를 살펴보고 마지막에 nil을 호출했을때는 deinit이 호출되었습니다.
-
하지만 이번에는 deinit이 호출되지 않습니다.
- 그림을 보면 알수있듯이 클래스와 객체의 참조는 관계를 읽었지만 클래스 인스턴스가 서로를 내부적으로 참조하고 있습니다. 이런상황이 만들어지게되면 이 두 객체의 메모리를 해제하는 방법은 존재 하지 않습니다.
- 이런상황을 Memory Leak 이라고 합니다.
- 그림을 보면 알수있듯이 클래스와 객체의 참조는 관계를 읽었지만 클래스 인스턴스가 서로를 내부적으로 참조하고 있습니다. 이런상황이 만들어지게되면 이 두 객체의 메모리를 해제하는 방법은 존재 하지 않습니다.
Weak
-
말그대로 약한. ' 약한 관계' 를 만드는 키워드 입니다.
-
아까 말했던 최소한 하나의 '참조'가 강한참조가 아니면 메모리에서 해제라는 말에서 '강한' 참조가 아니라 '약한' 참조라면 메모리에서 해제됩니다.
class TestClass{ weak var testClass: TestClass? = nil // 이제 이 참조는 약한 참조이다! init(){ print("init") } deinit{ print("deinit") } } var testClass1: TestClass? = TestClass() var testClass2: TestClass? = TestClass() testClass1?.testClass = testClass2 testClass2?.testClass = testClass1 testClass1 = nil // deinit testClass2 = nil // deinit
-
이러한 관계는 ARC가 메모리 카운트 하는데에 카운트를 하지 않으므로 메모리 해제가 가능해집니다.
-
한가지 주의 할점은 이렇게 참조되지 않는 변수는 자동으로 nil값이 됩니다.
따라서 weak 참조 변수는 반드시 optional타입이여야 합니다. -
Unowned 는 weak와 동일한 역할을 하지만 nil이 될수 없습니다. 따라서 optional로 선언될수 없으며 작성자가 nil이 될수가 없다는 강한 확신이 있는경우에만 사용될수 있습니다.
## 어떤 상황에서 발생하는 것일까?
-
-
1. Delegate 예시
-
보통 사용자가 루트뷰에서 서브뷰로 델리게이트를 만들때 발생합니다. 사용자가 생성한 포로토콜 델리게이트를 수행할때 weak 가 아니라면 강한 참조를 이루고 어미뷰컨트롤러가 pop된 이후 메모리 누수가 발생하게 됩니다.
-
UITableView의 정의를 보면 delegate와 dataSource 프로퍼티가 weak로 선언된것을 확인할수 있습니다.
2. Closures 예시
-
class TestClass{ var aBlock: (()->())? = nil let aConstant = 5 init(){ print("init") aBlock = { print(self.aConstant) } } deinit{ print("deinit") } } var testClass: TestClass? = TestClass() testClass = nil
-
위의 코드를 보면 aBlock는 옵셔널 클로저의 형태로 기본값으로 nil을 반환합니다.
-
하지만 클래스 생성시 초기화 형태를 보면 클로저는 aConstant를 참조하는데 nil 이 할당되도 내부에서 참조하고 있기 때문입니다. 따라서 weak self를 통해서 '약한 참조'를 만들어주면 deinit이 호출됩니다.
-
그러면 클로저를 쓸때 꼭 '약한 참조'를 사용해야 하는가에 대한 의문이 생길수 있는데 클로저를 사용할때에 클로저 안에서 클래스 내부의 인스턴스를 참조하지만 클로저 메서드안에서 생성되고 값이 리턴됨에 따라 레퍼런스가 해제되기 때문에 문제가 없습니다
class TestClass { let aConstant = 5 init() { print("init") let aBlock = { print(self.aConstant) } } deinit { print("deinit") } } var testClass: TestClass? = TestClass() testClass = nil // 클로저 내부에서는 self를 강하게 참조하지만 메소드가 리턴되면 메모리에서 해제됩니다.
-
## 메모리 누수를 어떻게 찾을수 있을까요?
deinit{
print("deinit"
}
- 이 소멸자는 해당 클래스 인스턴스의 메모리 해제가 필요할때 자동적으로 호출됩니다. ( 해제 직전 )
- 하지만 직접호출은 허용하지 않습니다.
- 보통 출력되게 감지하는것은 UIViewController가 pop될때 호출되는지 안되는지를 많이 체크 한다고 합니다
## 참고 블로그
baked-corn.tistory.com/30?category=718234
[Swift] Retain cycle, weak, unowned [번역]
[번역] Retain Cycles, Weak, Unowned in Swift Memory Management, Retain Cycle 그리고 weak, unowned 키워드의 사용법은 약간은 혼란스러운 주제일 수 있습니다. 반대로 이 주제를 이해하는 것은 매우 중요합니..
baked-corn.tistory.com
'IOS > iOS' 카테고리의 다른 글
[iOS] String Interpolation (0) | 2021.01.11 |
---|---|
[iOS]UTC Time To Date (feat. TimeZone) (2) | 2021.01.08 |
[TIL] 라이브러리, 프레임워크의 차이 (0) | 2020.12.21 |
[TIL] iOS MVC to MVVM Refactoring (0) | 2020.12.01 |
[TIL] iOS의 구조 (0) | 2020.11.29 |