2021년 7월 2주차
: 자동 참조 카운팅, 자동으로 메모리를 관리해주는 방식
- 객체의 lifetime을 계산하여 자동으로 컴파일 타임에
retain
/release
같은 메모리 관리 코드를 삽입해준다 retain
/release
는 컴파일 타임에 삽입되지만, 런타임에 실행된다- 클래스의 인스턴스를 만들 때마다 인스턴스 정보를 담는 메모리를 할당한다
- 인스턴스에 대한 참조 개수를 세어 해당 인스턴스에 대한 참조를 갖고 있는지 추적한다
- 최소 하나라도 그 인스턴스에 대한 참조가 있는 경우, 인스턴스를 메모리에서 해지하지 않는다
메모리 관리 기법 | ARC | GC |
---|---|---|
참조 카운팅 시점 | 컴파일 시 | 프로그램 동작 중 |
장점 | - 높은 성능 - 메모리를 적게 씀 (실행 중에 메모리 관리를 안 하기 때문) - 예측하기 좋음 |
- 대부분의 상황에서 인스턴스를 해제함 - 규칙을 신경 쓸 필요가 없어 사용하기 쉬움 |
단점 | - 특정 상황에서 메모리가 영원히 해제되지 않을 수 있음 ( ex. 순환참조 ) |
- CPU/메모리 요구를 정확하게 예측할 수 없음 - 대체로 ARC보다 성능이 낮음 |
- 별도의 식별자를 지정하지 않는 경우 기본적으로 강한참조를 한다
- 강한 참조 시, 참조 카운트가 1 증가
- 강한 참조 순환 문제로 메모리 leak을 야기한다
- 수동으로 해결 가능
- 약한 참조와 미소유 참조로 해결 가능
강한 참조 순환 문제
class Person {
let name: String
var room: Room?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class Room {
let number: String
var host: Person?
init (number: String) {
self.number =number
}
deinit {
print("Room \(number) is being deinitialized")
}
}
var lia: Person? = Person(name:"Lia") // Person 인스턴스의 참조 횟수: 1
var room: Room? = Room(number: "316") // Room 인스턴스의 참조 횟수: 1
room?.host = lia // Person 인스턴스의 참조 횟수: 2
lia?.room = room // Room 인스턴스의 참조 횟수: 2
lia = nil // Person 인스턴스의 참조 횟수: 1
room = nil // Room 인스턴스의 참조 횟수: 1
// Person 인스턴스를 참조할 방법 상실 - 메모리에 잔존
// Room 인스턴스를 참조할 방법 상실 - 메모리에 잔존
수동으로 해결 가능
var lia: Person? = Person(name: "Lia") // Person 인스턴스의 참조 횟수: 1
var room: Room? = Room(number: "316") // Room 인스턴스의 참조 횟수: 1
room?.host = lia // Person 인스턴스의 참조 횟수: 2
lia?.room = room // Room 인스턴스의 참조 횟수: 2
lia?.room = nil // Room 인스턴스의 참조 횟수: 1
lia = nil // Person 인스턴스의 참조 횟수: 1
room?.host = nil // Person 인스턴스의 참조 횟수: 0
// Lia is being deinitialized
room = nil // Room 인스턴스의 참조 횟수: 0
// Room 316 is being deinitialized
weak
키워드 사용- 참조하는 인스턴스의 참조 횟수를 증가시키지 않음
- 무조건 optional 변수여야 함 (nil이 할당될 수 있어야하기 때문)
class Room{
let number: String
weak var host: Person?
init(number: String) {
self.number = number
}
deinit {
print("Room \(number) is being deinitailized")
}
}
var lia: Person? = Person(name: "Lia") // Person 인스턴스의 참조 횟수: 1
var room: Room? = Room(number: "316") // Room 인스턴스의 참조 횟수: 1
room?.host = lia // Person 인스턴스의 참조 횟수: 1
lia?.room = room // Room 인스턴스의 참조 횟수: 2
lia = nil // Person 인스턴스의 참조 횟수: 0, Room 인스턴스의 참조 횟수: 1
// Lia is being deinitialized
print(room?.host) //nil
room = nil // Room 인스턴스의 참조 횟수: 0
// Room 316 is being deinitialized
unowned
키워드 사용- 참조하는 인스턴스의 참조 횟수를 증가시키지 않음
- 자신이 참조하는 인스턴스가 메모리에서 해제되어도 스스로 nil을 할당해주지 않음
- optional이 아니어도 됨
- 옵셔널 또는 암시적 추출 옵셔널 프로퍼티로 쓸 수도 있긴 함
- 메모리에서 해제된 인스턴스에 접근하려고 하면 강제 종료됨
- 그러므로 웬만하면 지양하자
class Person{
let name: String
var card: CreditCard?
// 카드를 소지할 수도, 소지하지 않을 수도 있기 때문에 옵셔널로 정의
// 카드를 한 번 가지면 참조를 잃지 않아야 하므로 강한참조를 해야함
init(name: String) {
self.name = name
}
deinit{ print("\(name) is being deinitialized")}
}
class CreditCard {
let number: UInt
unowned let owner: Person // 카드는 소유자가 분명히 존재해야함
init(number: UInt, owner: Person) {
self.number = number
self.owner = owner
}
deinit { print("Card #\(number) is being deinitialized")}
}
var lia: Person? = Person(name: "Lia") // Person 인스턴스의 참조 횟수: 1
if let person: Person = lia {
// CreditCard 인스턴스의 참조 횟수: 1
person.card = CreditCard(number: 1234, owner: person)
// Person 인스턴스의 참조 횟수: 1
}
lia = nil //Person 인스턴스의 참조 횟수: 0
// CreditCard 인스턴스의 참조 횟수: 0
// Lia is being deinitialized
// Card #1234 is being deinitialized
- 클로저는 참조 타입
- 클로저의 값 획득 특성 때문에 발생
- capture list 에 reference type 을 명시하여 해결 가능
문제 상황
class Person {
let name : String
lazy var introduce: () -> String = {
retrun "My name is \(self.name)"
}
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
var lia: Person? = Person(name: "Lia")
print(lia?.introduce) // My name is Lia
lia = nil
Capture List를 통한 해결
class Person {
let name : String
lazy var introduce: () -> String = { [weak self] in
return "My name is \(self?.name)"
}
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
var lia: Person? = Person(name: "Lia")
print(lia?.introduce) // My name is Lia
lia = nil // Lia is being deinitialized
- 스위프트 프로그래밍 3판 (야곰 저)
- Swift Language Guide
- ARC 뿌시기 블로그(컴파일타임에 실행되는 ARC가 동적인 객체를 관리할 수 있는 방법)
- when ARC working (stackoverflow)
- ARC vs GC (stackoverflow)