Swift 메모리 관리

  1. 메모리 관리는 객체를 생성하면 메모리를 차지하게 되는데 메모리의 공간은 한계가 있기 때문에 필요한 객체가 차지하는 메모리는 유지하고 필요없는 객체의 메모리를 해제하여 공간을 효율적으로 사용하도록 하는 방법이다.
    1. 객체가 사용 중인지 여부는 레퍼런스 카운트(Reference Count)가 관리한다.
      1. 객체를 사용하게되면 레퍼런스 카운트가 증가되고, 사용이 끝나면 레퍼런스 카운트를 감소한다.
      2. 만약 레퍼런스 카운트가 0이 되면, 객체를 사용하는 부분이 없어 메모리에서 해제를 하는 방식으로 메모리를 관리한다.
      3. 레퍼런스 카운트는 이전에는 수동으로 관리를 했으나 현재는 ARC(Automatic Reference Count)를 이용하여 자동으로 관리한다.
      4. ARC(Automatic Reference Count)
        1. 객체의 레퍼런스 카운트 관리 코드를 자동으로 생성하여 관리한다.
        2. 레퍼런스 타입(클래스) 객체에만 적용한다.
        3. Value Type(구조체, enum) 객체에서는 적용되지 않는다.
    2. 객체의 메모리 소유와 해제
      1. 메모리 소유 : 객체 생성 시에 메모리를 소유하게 된다.
        객체 생성
        class MyClass {
        }

        var ptr : MyClass! = MyClass()
        // 객체를 생성하여 메모리를 소유한다.
      2. 메모리 해제 : 객체에 nil을 대입하여 레퍼런스 카운트가 0이 되어 메모리에서 해제된다.
        객체 해제
        var ptr : MyClass! = MyClass()

        ptr = nil // 객체를 해제하여 메모리를 해제한다.
      3. 강한 참조 : 객체에 nil을 대입하면 무조건 메모리가 해제되는 것이 아니라 레퍼런스 카운트가 0이되어 메모리가 해제되는 것이며, 다른 객체를 참조하고 있다면 레퍼런스 카운트가 0이 되지 않아 메모리 해제가 발생되지 않는다.
        강한 참조
        var ptr : MyClass! = MyClass() // RC = 1

        var anotherPointer = ptr // RC = 2

        ptr = nil
        // RC = 1로 레퍼런스 카운트가 0이 되지 않아 메모리에서 해제되지 않는다.

        // RC = 0을 만들기 위해서 아래의 코드가 필요하다.
        // anotherPointer = nil : RC = 0이되어 메모리 해제 발생
      4. 메모리 해제 확인은 deinit 메소드로 확인한다.
        메모리 해제 확인
        class MyClass {
            deinit {
                print("메모리 해제")
            }
        }
    3. 강한 참조로 순환 참조
      1. 앞에 강한 참조를 이용하여 서로 다른 클래스가 상대방을 소유하고 있다면 레퍼런스 카운트가 0이 되지 않아 메모리가 할당된 상태가 유지가 된다.
      2. 해당 상황을 표현한 그림은 아래와 같다.
      3. 위 상황을 보면 Car 클래스는 Engine 클래스의 객체를 생성하고, Engine 클래스는 Car 클래스의 객체를 생성하여 서로 상대방의 클래스의 객체를 생성하여 서로 레퍼런스 카운트를 증가시켜주고 있다.
      4. 코드로 나타내면 아래와 같이 된다.
        강한 순환 참조
        class ClassA {
            var objB : ClassB!
            deinit {
                print("ClassA 객체 해제")
            }
        }

        class ClassB {
            var objA : ClassA!
           deinit {
                print("ClassB 객체 해제")
            }
        }

        var a : ClassA! = ClassA() // ClassA RC = 1
        var b : ClassB! = ClassB() // ClassB RC = 1

        a.objB = b // ClassB RC = 2
        b.objA = a // ClassA RC = 2

        a = nil
        // ClassA RC = 1 : RC 가 0이 되지 않아서 메모리 할당유지
        b = nil
        // ClassB RC = 1 : RC 가 0이 되지 않아서 메모리 할당유지
    4. 약한 참조
      1. 강한 순환 참조는 서로 상대방 클래스의 RC 를 늘려 정작 본인 클래스의 메모리 해제 시에 RC 가 0이 되지 않아서 메모리 해제가 발생되지 않는데 객체를 소유하지 않는 약한 참조(weak, unowned)를 이용하여 강한 참조를 발생하지 않을 수 있다.
      2. weak
        1. 참조하던 객체가 해제가 되면 자동으로 nil이 되는 구조이다.
        2. nil이 되므로 옵셔널 타입으로 선언한다.
        3. 상호 독립적으로 존재하는 객체에 사용한다.
        4. 사용 방법
          약한 참조 : weak
          // Person과 Phone이 서로 독립적인 존재
          class Person {
             weak var phone : Phone!
             deinit {
                 print("Person 객체 해제")
             }
          }

          class Phone {
             var person : Person!
             deinit {
                 print("Phone 객체 해제")
             }
          }

          var person : Person! = Person() // Person RC = 1
          var iphone : Phone! = Phone() // Phone RC = 1

          // 순환참조
          iphone.person = person // Person RC = 2
          person.phone = iphone
          // Phone RC = 1 : 약한참조로 소유하지 않음
          // 객체 해제 확인
          person = nil // Person RC = 1
          iphone = nil // Phone RC 0, Person RC = 0
          /* Person, Phone 클래스 모두 레퍼런스 카운트가 0이 되어 메모리 해제 발생 */
      3. unowned
        1. 참조하던 객체가 해제가 되어도 nil로 변하지 않아 Dangling Pointer의 위험이 있다.
        2. 옵셔널 타입으로 선언할 수 없어 Initializer 가 필요하다.
        3. 완전히 종속적인 경우에 사용한다.
        4. 사용 방법
          약한 참조 : unowned
          class Country {
              var capital : City!
          }

          class Capital {
             unowned var country : Country
             init(country:Country) {
                 self.country = country
             }
          }

          var korea : Country!  = Country() // Country RC = 1
          var seoul : Capital! = Capital(country:korea)
          // Country RC = 1 : 약한 참조로 소유하지 않음
          // Capital RC = 1
          // 순환 참조
          korea.capital = seoul
          // Capital RC = 2

          // 객체 해제 확인
          korea = nil // Country RC = 0
          seoul = nil
          // Capital RC = 1, Country RC = 0
          // Capital RC = 0
          /* Country, Capital 클래스 모두 레퍼런스 카운트가 0이 되어 메모리 해제 발생 */