정보/레벨 1

ARC(Automatic Reference Counting)의 동작 원리는 무엇인가요?

밤새는 탐험가89 2024. 11. 14. 05:41

 ARC(Automatic Reference Counting)는 Swift와 Objective-C에서 객체의 메모리를 관리하는 시스템입니다. ARC는 객체가 더 이상 필요하지 않게 될 때 자동으로 메모리를 해제하여 메모리 누수를 방지하고, 수동으로 메모리를 해제해야 하는 번거로움을 줄여줍니다. ARC의 동작 원리는 객체의 **참조 카운트(reference count)**를 기반으로 하며, 아래와 같은 방식으로 작동합니다.

 

1. 참조 카운트 기반 관리

  • ARC는 객체에 대한 **강한 참조(strong reference)**를 추적하여 객체가 얼마나 많이 참조되고 있는지 관리합니다.
  • 각 객체는 참조될 때마다 참조 카운트가 증가하고, 참조가 해제되면 참조 카운트가 감소합니다.
  • 참조 카운트가 0이 되면 객체가 더 이상 필요하지 않다고 판단하여 메모리에서 해제됩니다.

2. 강한 참조(strong reference)와 순환 참조 문제

  • 대부분의 경우, 객체는 강한 참조를 통해 다른 객체를 참조하며, 이때 참조 카운트가 증가합니다.
  • 그러나 두 객체가 서로 강한 참조를 할 경우, **순환 참조(retain cycle)**가 발생하여 참조 카운트가 0이 되지 않으므로 메모리에서 해제되지 않는 문제가 발생합니다.
  • 이 문제를 방지하기 위해 **약한 참조(weak reference)**와 **비소유 참조(unowned reference)**를 사용하여 ARC의 참조 카운트를 조정할 수 있습니다.

3. 약한 참조(weak reference)와 비소유 참조(unowned reference)

  • 약한 참조: 객체가 메모리에서 해제되면 자동으로 nil로 설정됩니다. ARC는 약한 참조를 참조 카운트에 포함하지 않으므로 순환 참조를 방지하는 데 유용합니다.
class Person {
    var name: String
    init(name: String) { self.name = name }
    deinit { print("\(name) is being deinitialized") }
}

class Apartment {
    var tenant: Person?
}

var john: Person? = Person(name: "John")
var apartment: Apartment? = Apartment()

apartment?.tenant = john // 강한 참조
john = nil // Person 인스턴스가 해제되지 않음

// 순환 참조 방지
apartment?.tenant = nil
  • 비소유 참조: 객체가 항상 메모리에 있다고 가정할 수 있을 때 사용하며, 참조 카운트에 포함되지 않습니다. 객체가 해제되었을 때도 nil이 아닌 값을 유지하므로 강제로 해제된 객체를 참조하면 런타임 오류가 발생할 수 있습니다.

 

4. ARC의 동작 과정 예시

  • 객체가 생성될 때, 참조 카운트는 1로 설정됩니다.
  • 다른 객체가 강한 참조를 통해 이를 참조하면 참조 카운트가 증가합니다.
  • 강한 참조가 해제되면 참조 카운트가 감소합니다.
  • 참조 카운트가 0이 되면, deinit 메서드가 호출되고 객체가 메모리에서 해제됩니다.
class Example {
    var data: String
    init(data: String) {
        self.data = data
        print("\(data) initialized")
    }
    deinit {
        print("\(data) is being deinitialized")
    }
}

var example: Example? = Example(data: "Sample Data") // 참조 카운트 1
example = nil // 참조 카운트 0, 메모리 해제 및 deinit 호출
 

이처럼 ARC는 참조 카운트를 관리하며, 객체가 더 이상 필요하지 않게 되면 자동으로 메모리를 해제하여 효율적으로 메모리를 관리합니다. ARC 덕분에 Swift와 Objective-C에서 메모리 관리가 간편해졌고, 명시적인 메모리 해제 코드가 필요 없게 되었습니다.

 

 

deinit 메서드는 언제 호출되며, 어떤 역할을 하나요?

deinit 메서드는 객체가 메모리에서 해제될 때 호출되는 소멸자입니다. Swift에서는 객체가 해제되기 직전에 deinit 메서드가 자동으로 호출되며, deinit은 다음과 같은 상황에서 유용하게 사용됩니다.

 

  1. 리소스 해제: 파일 핸들, 네트워크 연결, 데이터베이스 연결과 같은 리소스를 해제하는 데 사용됩니다.
  2. Observer 제거: NotificationCenter나 KVO에서 추가된 옵저버를 제거하여 메모리 누수를 방지합니다.
  3. 디버깅: 객체의 해제 여부를 확인하는 로그를 추가하여 retain cycle이 발생하는지 확인하는 데 유용합니다.

deinit 메서드는 오버라이드 없이 자동으로 호출되며, 파라미터나 반환값을 가질 수 없습니다.

class Example {
    init() {
        print("Example instance initialized")
    }
    
    deinit {
        print("Example instance deinitialized")
    }
}

var example: Example? = Example() // "Example instance initialized" 출력
example = nil                     // "Example instance deinitialized" 출력