본문 바로가기
CS/Computer Basic

[CS/Basic] 예시 코드와 함께 살펴보는 ARC 메모리 관리 과정

by iosdevlime 2023. 3. 17.

지난 포스팅에 이어..

편리하고 유용한 메모리 관리 모델인

 

ARC(Automatic Reference Counting)의 작동 과정

예시코드와 함께 알아보도록 하겠습니다.

 

 

 


 

 

ARC의 이해를 위한 예시 코드

ARC는 Reference Count의 증가와 감소를 통해 메모리를 관리, 해제하는 모델

 

ARC의 작동 과정을 살펴보기 위해,

아래와 같은 예시 코드를 활용하도록 하겠습니다.

Animals란 클래스가 있습니다. 이름(name) 프로퍼티를 담고 있습니다.
또한, RC의 증가와 감소, 해제를 확인하기 위해 init 및 deinit 메서드를 포함합니다.

 

class Animals {
    var name: String
    
    init(name: String) {
        self.name = name
        print("\(name)이 init 되었습니다.")
    }
    
    // 인스턴스가 메모리 상에서 제거(de-allocation)되었는지 확인하기 위해 deinit 메서드 활용
    deinit {
        print("\(name)이 deinit 되었습니다.")
    }
}

 

 

 

RC의 증가 (+) 

 

🔎 첫 번째, 새로운 인스턴스를 생성할 경우 RC는 증가합니다.

  • 아래와 같이 새로운 인스턴스인 doggy를 생성하게 되면 RC가 +1 증가합니다.
  • doggy 변수(주소값)은 Stack에 할당되며, Heap에 위치한 Animals Instatnce 값을 참조합니다.
var doggy: Animals = Animals(name: "Van") // RC +1

인스턴스를 생성할 경우, doggy 변수는 Heap 영역의 값을 참조(Reference)하게 되므로 RC가 1 증가함
인스턴스를 생성할 경우, doggy 변수는 Heap 영역의 값을 참조(Reference)하게 되므로 RC가 1 증가함

 

 

🔎 두 번째, 기존 인스턴스의 값을 다른 변수에 대입할 경우 RC는 증가합니다.

  • 기존 인스턴스인 'doggy'를 새로운 변수인 wildDoggy에 대입합니다.
  • 2개의 변수(doggy, wildDoggy)가 Heap의 Animals Instatnce를 참조하므로, RC가 +1 증가합니다.
var doggy = wildDoggy // RC +1

기존에 doggy란 변수가 대입된 wildDoggy는, Heap 영역의 값을 공유하여 참조하므로 RC가 1 증가함
기존에 doggy란 변수가 대입된 wildDoggy는, Heap 영역의 값을 공유하여 참조하므로 RC가 1 증가함

 

 


 

 

RC의 감소 (-) 및 메모리 해제

 

🔎 첫 번째, 특정 인스턴스를 가리키는 변수가 메모리 상에서 해제될 경우 RC는 감소합니다.

  • 함수 makePet'cat'이란 지역변수가 있으며, 매개변수인 origin이 할당되었습니다.
  • myCat이란 새로운 인스턴스를 생성한 후, makePet 함수의 매개변수로 할당한다면?
    • makePet 함수의 호출이 종료되면, Stack에 쌓인 지역변수 'cat'은 삭제됩니다.
    • 결과적으로, 참조 중이던 변수 cat이 삭제되는 동시에 RC -1 감소합니다.
func makePet(_ origin: Animals) {
    let cat = origin // cat이란 지역변수에 매개변수 값이 할당됨
}

var myCat:Animals = Animals(name: "Bebe") // myCat이란 새로운 인스턴스를 생성함 (RC + 1)

makePet(myCat) // makeCat 함수를 실행할 경우 -> makeCat 내부 cat 변수는 myCat을 참조하게 됨 (RC + 1)

// 그런데, 함수(메서드)가 종료되게 되면 Stack에 올라갔던 지역변수는 어떻게 된다? -> Stack에서 해제된다!
// 그렇게 되면, 참조하고 있었던 지역변수가 사라지므로, RC는 자동적으로 감소한다 (RC -1)

makePet 함수를 호출하게 되면, 🅐myCat 변수(인스턴스)와 함수 내부에서 추가로 참조된 🅑cat 변수 모두 Stack에 저장되며, RC값 또한 각각 증가한다
makePet 함수를 호출하게 되면, 🅐myCat 인스턴스와 함수 내부의 🅑cat 변수 모두 Stack에 저장, RC값 또한 각각 증가한다 (RC +2)

 

함수 종료 시 Stack의 경우 자동으로 메모리가 해제되므로, 참조하고 있던 'cat' 변수가 해제되는 동시에 RC값 -1 감소

 

 

🔎 두 번째, nil값이 할당되어 인스턴스의 값이 사라질 경우 RC는 감소합니다.

  • Optional 타입의 새로운 인스턴스인 cow를 생성합니다.
  • 기존 인스턴스(cow)를 할당받는 neighborCow란 변수를 생성한 후, 각각의 변수에 nil을 할당한다면?
    • 두 변수(인스턴스)는 Optional 타입이므로, 초기화 값이 해제됩니다.
    • 즉, 증가된 RC값은 모두 감소하며, 동시에 Animals 클래스 내부의 deinit 메서드가 실행됩니다.
var cow: Animals? = Animals(name: "Coby") // 옵셔널 타입의 cow를 생성하게 되면? (RC +1)
var neighborCow = cow // 기존 인스턴스를 할당받는 neighborCow란 새로운 변수가 생성된다면? (RC +1)

// 둘다 Optional 타입이므로, nil 값으로 초기값을 해제할 수 있음!
cow = nil // (RC -1)
neighborCow = nil // (RC -1) 
// RC가 0으로, 메모리 상 해제되었으므로 deinit 메서드에 따라 메세지가 log에 등장함

 

 

🔎 세 번째, 기존 인스턴스(변수)에 다른 인스턴스(변수) 값을 할당할 시, RC는 감소합니다.

  • Animals 클래스 타입의 🅐doyoungChild라는 새로운 인스턴스를 생성합니다.
  • 이후, 🅑jaejunChild라는 또 다른 인스턴스를 생성한 후, 기존 🅐변수에 🅑를 할당한다면? 
    • 🅐doyoungChild의 값이 변경되는 동시에 Stack 내 메모리는 삭제됩니다.
    • 따라서, RC는 -1 감소하는 동시에 Animals 클래스 내부의 deinit 메서드가 실행됩니다.
var doyoungChild: Animals? = Animals(name: "HaYesoul") // RC +1
var jaejunChild: Animals? = Animals(name: "JeonYesoul") // RC +1

doyoungChild = jaejunChild // HaYesoul이 deinit 되었습니다. (RC -1)
// doyungChild의 RC는 0이므로, 자동으로 해제됨

 

 

🔎 네 번째, 참조로 연결된 Stack의 주소값이 사라질 경우 RC는 감소합니다.

  • Human이란 클래스 내부엔 age 프로퍼티와 🅐nickname이란 클래스 인스턴스가 있습니다.
  • 🅐nickname 클래스 인스턴스는, Animals 클래스 타입을 가지며, 초기화 값을 가지고 있습니다.
class Human {
    var age: Int
    var nickname: Animals = Animals(name: "Kate")
    
    init(age: Int) {
        self.age = age
    }
    
    deinit {
        print("Human이 Deinit 되었습니다.")
    }
}

 

  • Optional 타입의 Human 인스턴스인 🅑profile을 생성한 이후, nil값을 할당한다면? 
    • 🅑profile이 참조하는 Human 인스턴스의 값은 삭제되므로, RC - 1 감소합니다.
    • 순서대로, Animals 타입의 🅐nickname 변수 또한 삭제되므로, RC -1 감소합니다.
var profile: Human? = Human(age: 25) // "kate가 init 되었습니다!"
// Human 인스턴스인 profile을 생성하게 될 경우 -> Heap 영역에 age와 nickname의 주소값이 포함되게 된다! (RC +1)
// 여기서 중요, Human 클래스 내부에 클래스 인스턴스가 일종의 '프로퍼티'로 존재하고 있음
// 따라서, profile 생성 시 Animals 인스턴스 또한 참조하게 됨 (RC +1)

Heap에는 기존 참조값인 Human 인스턴스 할당으로 RC +1, 동시에 nickname 프로퍼티가 가리키는 Animals 인스턴스로 인해 RC +1 증가
Heap에는 기존 참조값인 Human 인스턴스 할당으로 RC +1, nickname 프로퍼티가 가리키는 Animals 인스턴스로 인해 RC +1

 

// 만약, profile 인스턴스를 메모리에서 해제하기위해 nil 값을 할당하게 된다면
profile = nil

// 아래와 같이 순서대로 메모리가 해제됨 (동시에 해제되는 것이 아닌, 단계에 따라 RC가 감소하게 되면서 자연스럽게 해제됨)
// 첫 번째 -> Human이 Deinit 되었습니다. (RC -1)
// 두 번째 -> Kate이 deinit 되었습니다. (RC -1)

profile에 nil을 할당하는 순간, 기존 참조하고 있던 Human 인스턴스, 그리고 nickname이 가리키던 Animals 인스턴스가 차례대로 해제됨
profile에 nil을 할당하는 순간, 기존 참조하고 있던 Human 인스턴스, 그리고 nickname이 가리키던 Animals 인스턴스가 차례대로 해제

 

 

 


 

ARC의 메모리 관리 과정에 대해 구체적으로 알아본 시간이었습니다.



strong, weak, unowned, 순환참조

메모리 관리와 관련된 심화된 내용은 다음 포스팅에서 다루도록 하겠습니다 😭

댓글