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은 다음과 같은 상황에서 유용하게 사용됩니다.
- 리소스 해제: 파일 핸들, 네트워크 연결, 데이터베이스 연결과 같은 리소스를 해제하는 데 사용됩니다.
- Observer 제거: NotificationCenter나 KVO에서 추가된 옵저버를 제거하여 메모리 누수를 방지합니다.
- 디버깅: 객체의 해제 여부를 확인하는 로그를 추가하여 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" 출력
'정보 > 레벨 1' 카테고리의 다른 글
사용자 인터페이스(UI) 테스트와 단위(Unit) 테스트의 차이점은 무엇인가요? (2) | 2024.11.15 |
---|---|
상속(Inheritance)과 프로토콜(Protocol)의 차이점은 무엇인가요? (1) | 2024.11.14 |
UIKit에서 TableView와 CollectionView의 차이점은 무엇인가요? (0) | 2024.11.14 |
iOS 앱에서 Multi-threading을 구현하는 방법은 무엇인가요? (0) | 2024.11.13 |
메모리 관리에서 강한 참조(Strong Reference)와 약한 참조(Weak Reference)의 차이점은 무엇인가요? (0) | 2024.11.13 |