이번 포스팅에서는 ARC와 메모리 누수가 언제 일어나는지 알아 보도록 하자 

 

ARC 

ARCAutomatic Reference Counting의 약자로, 

말 그래도

Referencecounting 하여, 

자동으로 메모리를 관리 한다. 

 

또한 Reference라는 키워드에서 볼 수 있듯이, 

클로져나, 클래스와 같은 Reference 타입에 대해서만 관리 하며, 

enum, struct와 같은 Value 타입에 대해서는 관리하지 않는다. 

 

이들은 retain 이라는 함수를 호출하여 Reference Count를 증가 시키고, 

release라는 함수를 호출하여 Reference Count를 감소 시킨다. 

 

최종적으로 reference count가 0이 될때 자동으로 메모리에서 해제 해준다. 

 

class Person {
    let age: Int
    
    init(_ age: Int) {
        self.age = age
    }
    
    deinit {
        print("Person Class's Instance deinitialize")
    }
}


var p1: Person? = Person(10) // Reference Count = 1
var p2: Person? = p1 		 // Reference Count = 2
var p3: Person? = p1 		 // Reference Count = 3

위의 상황을 그림으로 표현하자면!

p1, p2, p3는 실제로 같은 객체를 참조하게 되는데, 

즉, 이 세개의 변수는 같은 주소 값을 저장하고 있다. 

 

따라서, p1 혹은 p2까지 nil을 저장 한다 해서 메모리에서 해제되지 않는다.

(해당 인스턴스가 메모리가 해제되는 시점에서 deinit 호출)

p1 = nil
p2 = nil

즉 아래와 같은 상황이다.

reference point 여전히 1이기 때문에 메모리에서 해제 되지 않는다. 

 

p1 = nil
p2 = nil
p3 = nil
//Person Class's Instance deinitialize

이렇게 nil로 저장되게 되면, 마침에 Reference Counting은 0이 되며, 

Person의 인스턴스는 메모리에서 해제 된다.

 

 

 

강한 참조

강한 참조란, 

Referencing Counting을 증가시키는 참조로서, 

위의 예제에서 썼던 참조가 모두 강한 참조이다. 

 

하지만, 강한 참조에서는

순환 참조라는 문제점이 존재하는데 이에 대해 알아보자

 

순환 참조

class Person {
    let age: Int
    var house: House?
    
    
    init(_ age: Int) {
        self.age = age
    }
    deinit {
        print("Person Class's Instance deinitialize")
    }
}

class House {
    let city: String
    var owner: Person?
    
    init(_ city: String) {
        self.city = city
    }
    deinit {
        print("House Class's Instance deinitialize")
    }
}

var jung: Person? = Person(25)
var someHouse: House? = House("seoul")

jung?.house = someHouse
someHouse?.owner = jung

 

해당 경우는 순환 참조가 일어난 상황인데, 이를 그림으로 보면

이렇게 Person , House의 인스턴스의  Reference Counting 이 2가 되는 상황이다. 

 

해당 경우에

jung = nil 	// Person Instance => reference Count = 1
someHouse = nil // House Instance => reference Count = 1

이렇게 해제 해주어도 deinit이 호출되지 않는데 그 이유는, 

instance의 저장 프로퍼티끼리 일어난 참조를 해제해주지 않았기 때문이다.

 

즉, 메모리 누수가 발생하게 된다!

 

이를 해결하지 위해, weak, unowned 참조가 있다.

 

 

약한(weak) 참조 

앞에 weak키워드를 사용한다. 

class Person {
    ...
    weak var house: House?
    ...
}

약한 참조는 Reference Count를 증가시키지 않고,

참조하고 있는 인스턴스가 메모리에서 해제되면, 자동으로 nil이 할당이 된다. 

 

따라서, Optional 타입이어야 한다.

 

jung = nil
someHouse = nil
//House Class's Instance deinitialize
//Person Class's Instance deinitialize

해당 경우에 deinit이 둘 다 잘 동작된다. 

먼저 Person Instance의 Reference Count는 1이 되고, (House Instance내부의 강한 참조)

House Instance의 Reference Count는 0이 되고, 메모리에서 해제된다. 

해제됨에 따라, House Instance 내부의 강한 참조 역시 해제 되며! 

Person Instance 의 Reference Count는 0이 되게 되어, 메모리에서 해제된다.

 

 

미소유(unowned) 참조

미소유 참조 역시 Reference Count를 증가시키지 않지만,

참조하고 있는 instance가 먼저 해제되지 않아야 한다!

 

즉, Optional 타입이 아니며, 

참조하고 있는 Instance가 해제 되더라도, 여전히 주소 값을 저장하고 있으며

접근하게 되면 에러가 난다. 

 

class Person {
    ...
    unowned var house: House
    ...
}

해당 경우에 House instance가 먼저 해제되더라고, 

house 에는 여전히 House Instance의 참조 값이 존재하고, 

이를 통해 접근하게 되면, 에러가 난다.

 

 

Reference

Automatic Reference Counting

복사했습니다!