IOS/iOS

[TIL][iOS] Retain Cycle, Weak, Unowned ( feat. ARC )

## 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