ARC-간단정리
2023. 2. 7. 22:37ㆍSwift
반응형
해당 글은 공식문서를 참고했으며, 번역 혹은 개인생각이 들어가 있어 정확하지 않은 정보를 제공할 수 있습니다
참고 문서
ARC(Automatic Reference Counting)
- Swift는 메모리를 관리하기위해 ARC를 사용한다.
- ARC는 인스턴스가 필요치 않을 경우 자동으로 메모리에서 할당 해제한다
- ARC는 참조타입의 경우에만 적용된다
ARC가 어떻게 작동하는지
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) 생성")
}
deinit {
print("\(name) 해제")
}
}
- 해당
Person
클래스는 초기 생성시와 메모리에서 해제되는 시점에서 해당name
을 출력하도록 작성하였다
var a: Person?
var b: Person?
var c: Person?
a = Person(name: "John")
- 해당 3가지의 Person타입에 a에 John이라는 이름의
Person
타입을 선언해주었다. 이럴 경우 Reference Count는 1이된다.
b = a
c = a
- b와 c의 변수에는 이전에 생성했던 John이라는
Person
을 할당시켜주며 John의Person
은 Reference Count가 3이된다.
a = nil
b = nil
c = nil
- 해당 변수들에 nil을 할당할 경우 참조타입이 하나씩 줄어들면서 최종적으로 마지막 c까지 nil이 되면 John의
Person
을 더이상 사용하지 않는게 분명하므로Person
의deinit
이 실행되게 된다
Strong Reference Cycles Between Class Instances
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
Person
과Apartment
의 클래스는 각각 서로를 참조하고 있으며 해당 부분은 Reference Counting이 0이 될 경우deinit
이 되게끔 작성되어있다
var john: Person?
var unit4A: Apartment?
john = Person(name: "John")
unit4A = Apartment(unit: "1201")
john?.apartment = unit4A
unit4A?.tenant = john
- 이 부분은 john과 unit4A가 서로 참조하는 형태로 관계가 맺어져있다.
john = nil
unit4A = nil
- 만약 이렇게 john과 unit4A가 nil로 할당될 경우 각각 인스턴스가 사라지기 때문에
deinit
이 되어야 할 것 같지만deinit
이 실행되지도 않을뿐더러 메모리에도 남아있게 된다.
- 이유는 기존
Person
의apartment
가unit4A
를 참조하고 있으며 마찬가지로unit4A
의tennant
역시Person
인스턴스를 참조하고 있기 때문에 Reference Counting이 0이 되지 않아 메모리에서 해제되지 않는것이다
john?.apartment = nil
john = nil
unit4A?.tenant = nil
unit4A = nil
- 이 경우 각각 타입의 프로퍼티에 nil을 할당하고 해당 타입에 nil을 할당해야 메모리에 해제된다.
- 이것을 해결하기 위해 weak, unowned를 사용한다
Weak Reference
- 약한 참조는 위의 강한참조 문제를 해결할 수 있다.
- 다만 약한 참조를 사용하기 위해서는 nil로 변경되어야 하기 때문에 항상 옵셔널 타입의 변수로 설정되어야 한다
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
- 위
Apartment
의Person
타입을 참조하는tenant
를 약한 참조로 설정해 주었다.
- 이 경우
john
과unit4A
는 서로 참조를 하고 있지만unit4A
의tenant
는john
을 약한참조한다.
john = nil //print John is being deinitialized
print(unit4A?.tenant) // print nil
- 약한 참조이므로
John
에 nil이 할당 될 경우 약한 참조로 인해unit4A
의tenant
가 nil이 되며john
은deinit
된다.
Unowned References
- 미소유 참조는 약한참조와 유사하게 인스턴스를 강한참조 하지는 않는다
- 미소유 참조의 경우 약한참조에 비해 인스턴스의 수명이 같거나 더 긴 경우에 사용된다
- 약한 참조와 달리 미소유 참조는 항상 값을 가지도록 해야한다. 즉 옵셔널 값으로 만들어지면 안된다.
미소유 참조는 절대 할당 해제되지 않는 인스턴스일때만 사용한다.
만약 참조하고 있는 인스턴스가 할당 해제되어서 미소유 참조 값에 접근하려 한다면 런타임 에러가 발생하게 된다.
- 예제로는 고객과 신용카드 객체가 있다.
- 고객은 신용카드를 안가지고 있을 수 있지만, 신용카드 객체는 항상 고객을 가지고 있어야 하므로 미소유 참조로 고객을 참조하는 형태를 지니기에 적합하다
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
var john: Customer?
john = Customer(name: "John")
john?.card = CreditCard(number: 12345678, customer: john!)
- 이 경우에
john
에 nil을 할당 할 경우John
과card
프로퍼티는 각각 메모리에서 헤제된다.
john = Customer(name: "John")
let bcCard = CreditCard(number: 12345678, customer: john!)
john?.card = bcCard
john = nil
- 다만 위와 같이
CreditCard
의 인스턴스를 만들어John
의 프로퍼티에 할당하여John
을 nil로 변경할 경우john
은 메모리에서 해제된다.
print(bcCard.customer) // Runtime Error Thread 1: signal SIGABRT
- 단순히
bcCard
인스턴스를 접근하는거는 문제가 되지 않지만 해당 인스턴스의customer
을 접근하려고 한다면 위에 말했듯이 런타임에러가 뜨게 된다.
Strong Reference Cycles for Closures
- 클로저의 경우 캡처로 인하여 강한참조에 대한 문제가 발생할 수 있습니다
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
- 이 경우
asHTML
의 클로저는 해당 클래스의 프로퍼티name
과text
를 캡처한다.
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"
해당 클로저는
HTMLElement
의 프로퍼티들을 참조하므로 위와 같이 강한참조 문제가 발생하게 된다.이 경우
paragraph
의 인스턴스를nil
로 설정하여도 해당 클로저에 대한 강한참조 문제때문에 클로저는 메모리에서 해제되지 않는다.위 같은 경우 캡처한 인스턴스가 항상 서로를 참조하고 클래스 내에 정의되어 있기 때문에 같은 시간에 메모리가 해제되도록 미소유 참조를 사용하는것이 좋다.
만약 약한 참조를 사용할 경우, 해당 인스턴스가 항상 옵셔널타입이므로 인스턴스가 nil이 된다면 클로저 역시 nil이 되어 자동으로 해제된다.
lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
- 위 경우 해당
self
를 강한참조가 아닌 미소유 참조로 캡처를 하여 사용을 하게 된다면 해당 클래스가 nil이 될 경우 클로저 역시 같이 nil이 되어 메모리에서 해제되게 된다.
반응형
'Swift' 카테고리의 다른 글
JSON Decoding 재활용 (0) | 2023.03.03 |
---|---|
Swift 컴파일, 컴파일러 (0) | 2023.02.14 |
Closures 간단 정리 (2) | 2023.02.06 |
정규표현식(Swift) (0) | 2023.01.29 |
KVC(Key-value coding), KVO(Key-value observing) (0) | 2023.01.22 |