Automatic Reference Counting
참조 타입 → 메모리 해제는 중요한 문제
→ 인스턴스가 적절한 시점에 메모리에서 해제되지 않으면 메모리 자원 낭비 → 성능 저하
스위프트는 메모리 사용을 관리하기 위해 메모리 관리 기법인 ARC를 사용
Reference Counting → 참조 횟수 계산
Reference Counting 은 참조 타입인 *클래스의 인스턴스***에만 적용
→ 구조체, 열거형은 값 타임이므로 적용 불가
→ Java 의 Garbage Collection 기법과 어떤 차이?
ARC는 컴파일 타임(compile time) 에 기존에 개발자가 직접 코드를 작성해야 되던 부분을 자동으로 구문을 분석해서 적절하게 레퍼런스 감소 코드삽입해 주어, 실행 중에 별도의 메모리 관리가 이루어 지지 않는다
가비지 컬렉션( GC
, Garbage Collection
)은 프로그램 실행 중(Runtime)에 동적으로 감시하고 있다가, 더 이상 사용할 필요가 없다고 여겨지는 것을 소멸(해제) 시켜버린다.
** 즉, ARC는 컴파일과 동시에 인스턴스를 메모리에서 해제하는 시점이 아예 결정되어 버린다.
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: "yagom")
// yagom is being initialized
// 인스턴스의 참조 횟수 : 1
reference2 = reference1 // 인스턴스의 참조 횟수 : 2
reference3 = reference1 // 인스턴스의 참조 횟수 : 3
reference3 = nil // 인스턴스의 참조 횟수 : 2
reference2 = nil // 인스턴스의 참조 횟수 : 1
reference1 = nil // 인스턴스의 참조 횟수 : 0
// yagom is being deinitialized
→ 참조 횟수가 0이 되는 순간 인스턴스는 ARC의 규칙에 의해 메모리에서 해제되며 메모리에서 해제되기 직전에 deinitializer 를 호출한다.
func foo() {
let yagom: Person = Person(name: "yagom") // yagom is being initialized
// 인스턴스의 참조 횟수 : 1
// 함수 종료 시점
// 인스턴스의 참조 횟수 : 0
// yagom is being deinitialized
}
foo()
→ 지역변수의 참조 횟수 확인
: 강한참조 지역변수가 사용된 범위의 코드 실행이 종료되면 그 지역변수가 참조하던 인스턴스의 참조 횟수가 1 감소함
class Person {
let name: String
init(name: String) {
self.name = name
}
var room: Room?
deinit {
print("\(name) is being deinitialized")
}
}
class Room {
let number: String
init(number: String) {
self.number = number
}
var host: Person?
deinit {
print("Room \(number) is being deinitialized")
}
}
var yagom: Person? = Person(name: "yagom") // Person 인스턴스의 참조 횟수 : 1
var room: Room? = Room(number: "505") // Room 인스턴스의 참조 횟수 : 1
room?.host = yagom // Person 인스턴스의 참조 횟수 : 2
yagom?.room = room // Room 인스턴스의 참조 횟수 : 2
yagom = nil // Person 인스턴스의 참조 횟수 : 1
room = nil // Room 인스턴스의 참조 횟수 : 1
// Person 인스턴스를 참조할 방법 상실 - 메모리에 잔존
// Room 인스턴스를 참조할 방법 상실 - 메모리에 잔존
→ 메모리 누수 발생
room?.host = yagom // Person 인스턴스의 참조 횟수 : 2
yagom?.room = room // Room 인스턴스의 참조 횟수 : 2
yagom?.room = nil // Room 인스턴스의 참조 횟수 : 1
yagom = nil // Person 인스턴스의 참조 횟수 : 1
room?.host = nil // Person 인스턴스의 참조 횟수 : 0
// yagom is being deinitialized
room = nil // Room 인스턴스의 참조 횟수 : 0
// Room 505 is being deinitialized
→ 하지만 이렇게 하나하나 해제시키는 것은 힘들다.
더 깔끔한 방법은 아래의 약한참조
** 옵셔널 변수만 약한 참조 가능
→ 메모리 해제 시 nil 이 할당되어야 하는데 상수에는 불가능
ex. 강한참조 순환 문제를 약한참조로 해결
class Person {
let name: String
init(name: String) {
self.name = name
}
var room: Room?
deinit {
print("\(name) is being deinitialized")
}
}
// 코드 27-6 강한참조 순환 문제를 약한참조로 해결
class Room {
let number: String
init(number: String) {
self.number = number
}
weak var host: Person?
deinit {
print("Room \(number) is being deinitialized")
}
}
var yagom: Person? = Person(name: "yagom") // Person 인스턴스의 참조 횟수 : 1
var room: Room? = Room(number: "505") // Room 인스턴스의 참조 횟수 : 1
room?.host = yagom // Person 인스턴스의 참조 횟수 : 1
yagom?.room = room // Room 인스턴스의 참조 횟수 : 2
yagom = nil // Person 인스턴스의 참조 횟수 : 0, Room 인스턴스의 참조 횟수 : 1
// yagom is being deinitialized
print(room?.host) // nil
room = nil // Room 인스턴스의 참조 횟수 : 0
// Room 505 is being deinitialized
약한참조와 마찬가지로 인스턴스의 참조 횟수를 증가시키지 않는다.
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 jisoo: Person? = Person(name: "jisoo") // Person 인스턴스의 참조 횟수 : 1
if let person: Person = jisoo {
// CreditCard 인스턴스의 참조 횟수 : 1
person.card = CreditCard(number: 1004, owner: person)
// Person 인스턴스의 참조 횟수 : 1
// 증가하지 않음
}
jisoo = nil // Person 인스턴스의 참조 횟수 : 0
// CreditCard 인스턴스의 참조 횟수 : 0
// jisoo is being deinitialized
// Card #1004 is being deinitialized
→ CreditCard 의 owner 프로퍼티가 미소유참조임.
(출처: docs.swift.org)
The Customer
instance now has a strong reference to the CreditCard
instance, and the CreditCard
instance has an unowned reference to the Customer
instance.
Because of the unowned customer
reference, when you break the strong reference held by the john
variable, there are no more strong references to the Customer
instance:
Because there are no more strong references to the Customer instance, it’s deallocated. After this happens, there are no more strong references to the CreditCard instance, and it too is deallocated