ARC와 메모리 누수(Automatic Reference Counting)
💡 ARC와 메모리 누수(Automatic Reference Counting)
ARC
정의 : "Automatic Reference Counting"의 약자
기능 : 개발자가 메모리 할당 및 해제를 명시적으로 처리할 필요 없이 객체의 참조를 추적하여 메모리를 자동으로 관리
동작 방식 : 객체의 참조 카운트를 추적, 더 이상 참조되지 않는 객체는 자동으로 메모리에서 해제
목적 :
메모리 누수를 방지하여 프로그램이 더 이상 필요하지 않은 메모리를 해제하지 않고 남겨두는 상황 예방
주의사항 : 모든 종류의 메모리 누수를 방지해주진 않으며, 순환 참조와 같은 특정 상황에서는 개발자가 약한 참조(Weak Reference)와 비소유 참조(Unowned Reference)를 적절히 사용하여 메모리 누수를 방지해야 함
참조 카운팅
객체의 참조를 추적하여 참조 카운트를 관리
→ 각 객체는 현재 참조하고 있는 다른 객체의 수를 나타내는 카운트를 가지고 있음
참조 증가 및 감소
→ 객체가 새로운 참조를 받으면 해당 객체의 참조 카운트가 증가
→ 참조가 해제되면 참조 카운트가 감소
참조 해제
객체의 참조 카운트가 0이 되면 해당 객체는 더 이상 필요하지 않다고 판단되어 메모리에서 자동 해제
순환 참조 처리
순환 참조를 방지하기 위해 약한 참조(Weak Reference)와 비소유 참조(Unowned Reference)를 제공
→ 이를 통해 순환 참조로 인한 메모리 누수를 방지할 수 있음
컴파일러 지원
컴파일러 수준에서 코드를 분석하여 메모리 관리 보장
→ 따라서 개발자가 명시적으로 메모리 관리를 할 필요가 없음
성능 최적화
ARC는 객체의 수명 추적을 효율적으로 수행하여 메모리 관리 오버헤드 최소화
예시
클래스 Person
을 정의하고, 그 안에 초기화(init
) 및 해제(deinit
) 메시지를 출력
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1 = Person(name: "John Appleseed") // RC: 1️⃣
// Prints "John Appleseed is being initialized"
reference2 = reference1 // RC: 2️⃣
reference3 = reference1 // RC: 3️⃣
reference1 = nil // RC: 2️⃣
reference2 = nil // RC: 1️⃣
reference3 = nil // RC: 0️⃣
// Prints "John Appleseed is being deinitialized"
Person 클래스는 인스턴스의 name 프로퍼티를 설정, 해당 인스턴스가 초기화됨을 알리는 메시지 출력
→ 소멸자 또한 가지고 있기 때문에 소멸자 실행시에도 클래스 인스턴스가 해제됨을 알리는 메시지 출력
아래 인용된 코드들은 Person?
타입의 변수를 정의
→ 다음 코드들의 새로운 Person
인스턴스를 위한 다수의 참조를 설정하기 위해 사용될 것
// Code(01) | Person? 타입 변수 정의
var reference1: Person?
var reference2: Person?
var reference3: Person?
위 코드Code(01) 중 하나의 변수에 새로운 Person
클래스 인스턴스를 생성하여 할당할 수 있음
// Code(02) | 새로운 Person 클래스 인스턴스
reference1 = Person(name: "John Appleseed") // RC: 1️⃣
// Prints "John Appleseed is being initialized"
Person
클래스의 생성자 호출 시점에 'John Appleseed is being initialized' 가 출력, 이는 새로운 인스턴스 생성이 완료됨을 알림
→ 새로운 Person
인스턴스가 reference1
변수에 할당되었기 때문에 reference1
은 새로운 Person
인스턴스에 대한 참조가 생김
→ 최소한 하나의 강한 참조가 있으므로, ARC는 해당 Person
인스턴스가 메모리에서 유지되며 메모리에서 해제되지 않도록 보장함
만약 동일한 Person
인스턴스를 2개의 변수에 추가 할당한다면?
→ 해당 인스턴스에 대한 두개의 강한 참조가 추가로 생성됨
// Code(03) | 동일한 Person 인스턴스를 2개의 변수에 추가
reference2 = reference1 // RC: 2️⃣
reference3 = reference1 // RC: 3️⃣
이제 하나의 Person
인스턴스에 대한 총 3개의 강한 참조가 존재
→ 만약, 3개의 강한 참조 중 두 개의 강한 참조를 없애기 위해 두 개의 참조변수에 nil
을 할당한다면 1개의 강한참조가 남게 됨
→ 최소 1개의 참조만 남아있음 → Person
인스턴스 메모리에서 해제되지 않음
// Code(04) | 하나의 Person 인스턴스에 대한 총 3개의 강한 참조가 존재
reference1 = nil // RC: 2️⃣
reference2 = nil // RC: 1️⃣
ARC는 Person
인스턴스에 대한 마지막 1개의 강한 참조가 깨지기(Person
인스턴스를 더이상 사용하지 않게 될 명확한 시점) 전까지는 해당 인스턴스를 해제하지 않음
메모리 누수
- 애플리케이션이 더 이상 필요하지 않은 메모리를 제대로 해제하지 못하여 메모리 사용량이 계속해서 증가하는 상황
- 객체나 리소스가 더 이상 필요하지 않은데도 메모리에서 해제되지 않으면 메모리 누수 발생
강한 참조 순환 (Strong Reference Cycle)
두 개 이상의 객체가 서로 강한 참조를 유지하여 순환 구조를 만들 때 발생
→ 이러한 순환 구조로 인해 객체들이 해제되지 못하고 메모리에 계속해서 남게 됨
클로저에서 강한 참조 (Strong Reference in Closures)
객체를 강하게 캡처하면서 클로저와 객체 간에 강한 참조 순환을 만들 수 있음
→ 이로 인해 객체가 해제되지 못하고 메모리에 계속해서 유지됨
약한 참조(Weak Reference)
순환 참조를 방지하기 위해 객체 간의 관계 중 하나를 약한 참조로 선언
→ 이렇게 하면 참조하는 객체의 수명이 다른 객체의 수명에 의존하지 않게 되어 순환 참조를 방지할 수 있음
비소유 참조(Unowned Reference)
약한 참조와 유사하지만, 객체가 해제된 후에도 nil
로 설정되지 않음
→ 객체의 생명주기가 참조하는 객체의 생명주기와 동일하거나 더 길 때 사용
클로저 캡처 리스트(Capture Lists)
클로저 내에서 발생하는 순환 참조를 방지하기 위해 캡처 리스트를 사용하여 클로저가 강한 참조를 보유하지 않도록 함
→ 클로저 내에서 self
를 캡처할 때 캡처 리스트를 활용하여 약한 참조로 캡처할 수 있음
강한 참조 사이클 해제(Strong Reference Cycle Resolution)
강한 참조 사이클이 발생했을 때, 객체들 간의 관계를 적절히 변경하여 사이클을 해제할 수 있음
→ 이를 통해 각 객체가 서로를 강한 참조하지 않도록 조정하여 메모리 누수 방지
수동 해제(Manual Deallocation)
필요한 경우에는 객체를 수동으로 해제할 수 있음
→ 이는 특히 순환 참조를 해결하기 어려운 복잡한 상황에서 유용할 수 있음
예시
강한 참조 순환 해결
class Person {
var name: String
var car: Car?
init(name: String) {
self.name = name
print("\(name) is initialized.")
}
deinit {
print("\(name) is deallocated.")
}
}
class Car {
weak var owner: Person?
init() {
owner = nil
print("Car is initialized.")
}
deinit {
print("Car is deallocated.")
}
}
var john: Person? = Person(name: "John")
var car: Car? = Car()
john?.car = car
car?.owner = john
john = nil
car = nil
순환 참조(Circular References)
두 개 이상의 객체가 서로를 간접적으로 또는 직접적으로 참조하는 경우 발생
→ 객체들 간의 참조 카운트가 0이 되지 않아 메모리가 해제되지 않음
→ 이를 해결하기 위해 강한 참조를 약한 참조로 변경하거나, 강한 참조를 수동으로 해제해야 함
클로저와 순환 참조(Closures and Retain Cycles)
클로저 내에서 self를 캡처하고 해당 클로저가 self를 강한 참조로 보유하는 경우 발생
→ 클로저에서 약한 참조로 self
를 캡처하거나, 클로저가 더 이상 필요하지 않을 때 적절히 해제해야 함
객체 해제 후에 접근(Object Deallocation and Access)
객체를 사용하는 동안 해당 객체가 해제되었는지 확인하지 않고 접근하려고 할 때 발생
→ 이는 애플리케이션의 여러 부분에서 발생할 수 있으며, 주로 비동기 작업에서 발생함
유형 | 설명 | 해결 방법 |
순환 참조 (Circular References) |
두 개 이상의 객체가 서로를 간접적으로 또는 직접적으로 참조하여 참조 카운트가 0이 되지 않는 경우 발생 | - 강한 참조를 약한 참조로 변경 - 강한 참조를 수동으로 해제 |
클로저와 순환 참조 (Closures and Retain Cycles) |
클로저 내에서 self를 캡처하고 해당 클로저가 self를 강한 참조로 보유하는 경우 발생 | - 클로저에서 약한 참조로 self를 캡처 - 클로저가 더 이상 필요하지 않을 때 적절히 해제 |
객체 해제 후에 접근 (Object Deallocation and Access) |
객체를 사용하는 동안 해당 객체가 해제되었는지 확인하지 않고 접근하려고 할 때 발생 | - 객체에 접근하기 전에 해당 객체가 존재하는지 확인 - 비동기 작업에서 안전하게 객체 접근하기 위해 콜백이나 완료 핸들러를 사용 |
' 𝗔𝗣𝗣𝗟𝗘 > SWIFT : GRAMMAR' 카테고리의 다른 글
Swift 기초 문법 - 확장(Extension) (0) | 2024.03.14 |
---|---|
Swift 기초 문법 - 프로토콜(Protocol) (0) | 2024.03.14 |
Swift 기초 문법 - 예외 처리(Exception Handling) (0) | 2024.03.13 |
Swift 기초 문법 - 고차함수(higher-order function) (0) | 2024.03.13 |
Swift 기초 문법 - 클로저(Closure) (0) | 2024.03.13 |