공부한 것을 정리하는 용도의 글이므로 100% 정확하지 않을 수 있습니다.
참고용으로만 봐주시고, 내용이 부족하다고 느끼신다면 다른 글도 보시는 것이 좋습니다.
+ 틀린 부분, 수정해야 할 부분은 언제든지 피드백 주세요. 😊
by. ryalya
Objective C를 사용했던 때는 메모리 관리를 어떻게 했을까?
힙에 메모리를 직접 할당/해제
Cocoa Framework 에서 레퍼런스 증가는
alloc, new, copy, mutableCopy, retain 등을 사용, 레퍼런스 감소는 release 사용
Swift에서는 ARC만이 사용되기때문에 Objective-C를 사용하지 않는다면 자세히 이해할 필요는 없음.
(다만 Objective-C 코드를 만져야한다면 알아야 함....)
Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. In most cases, this means that memory management “just works” in Swift, and you do not need to think about memory management yourself. ARC automatically frees up the memory used by class instances when those instances are no longer needed.
RC 자동 관리 방식 (WWDC 2011 발표)
iOS에서 메모리 관리하는 방법으로 자동으로 메모리를 관리해줌. (memory leak 방지)
컴파일(build)할 때, 코드를 분석해서 자동으로 retain, release 코드를 생성해줌.
메모리에 참조된 횟수를 추적해(계산해) 더 이상 참조되지 않는 인스턴스(참조 횟수가 0이 되면)를 deinit
을 호출하여 메모리에서 해제해줌.
힙(heap)에 할당된 인스턴스의 메모리를 자동으로 해제해줌.
retain cycle에는 유의해야 함.
* Heap : class, closure 등의 참조형(reference) 자료 들이 머무는 공간이자, 개발자가 동적으로 할당하는 메모리 공간.
Application은 Compile, Linking, Runtime (linking)을 거쳐 빌드되어 실행된다.
인스턴스는 하나 이상의 소유자(Owner)가 있는 경우 메모리에 유지된다.
위의 ARC 특징에서 언급한 것 처럼 소유자가 없다면 그 즉시 메모리에서 제거된다.
제거 시점을 파악하기 위하여 소유자 수를 저장하는데 이것을 참조 카운트(Reference Count)라고한다.
참조 카운트가 1 이상이면 메모리에 유지되고, 0 이 되면 메모리에서 제거된다.
클래스 인스턴스를 변수에 저장하면 변수가 소유자가되고 이 때 참조 카운트는 1이 증가하는 방식으로 만약 또 다른 변수에 저장하면 2가 된다.
코드 레벨로 보자면 해당 인스턴스의 retain 메소드를 호출하는 것과 같음.
참조 방식에는 3가지가 있다.
<간단 예시>
var person = Person() // RC 1 증가
person = nil // RC 1 감소, RC = 0 , 메모리 해제
<예시>
class Person {
var name: String?
var age: Int?
init(name: String?, age: Int?) {
self.name = name
self.age = age
}
deinit {
print("\(name) is being deinitialized")
}
}
let circle = Person(name: "one", age: 25)
<예시2>
var person1: Person?
var person2: Person?
person1 = Person(name: "harry") // RC 1 증가 → 총 카운트 1
// Prints "harry is being initialized"
person2 = person1 // RC 1 증가 → 총 카운트 2
// nil = 소유권 포기, 즉시 강한 참조가 제거됨
person1 = nil // 참조카운트 1 감소 → 총 카운트 1
// 현재 RC = 1 이므로 인스턴스가 제거되지 않음.
person2 = nil // 참조카운트 1 감소 → 총 카운트 0
// Prints "\harry is being deinitialized"
// RC = 0. 인스턴스 해제
? 문제점 ?
→ strong으로 선언된 변수들이 순환참조 됐을 시, 서로가 서로를 참조하고 있어서 변수를 nil로 지정해도 RC가 0이 되지 못한다.
또한, 해당 인스턴스를 가리키던 변수도 nil이 지정됐기 때문에 인스턴스에 접근 할 수 있는 방법도 없어 메모리 해제도 할 수 없다.
→ Strong 참조로 인한 순환참조를 강한 순환 참조라고 함.
→ 즉, 어플이 죽기 전까지 memory leak이 계속 발생하게 됨.
→ 이런 순환 참조의 문제점을 해결하기 위해서 weak, unowned를 사용할 수 있다.
<간단 예시>
weak var person = Person() // 객체가 생성 되지만 weak이기 때문에 바로 객체가 해제되어 nil이 됨
→ 사라지지 않을 거라고 보장되는 객체에만 설정하는 것이 crash가 발생하는 것을 방지할 수 있음.
weak는 객체를 계속 추적하고 있음.
이렇게 계속 객체를 추적하고 있는 것도 런타임시 오버헤드가 될 수 있음.
따라서 확실한 객체에는 unowned를 사용하는 것이 좋음.