일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- web
- 암호수학
- windosw 문자열
- System
- 파이썬
- 두근두근 자료구조
- LoB
- PHP
- ftz level13
- War Game
- pwnable.kr
- 자료구조
- windosws wbcs
- 백준
- OSI
- 파일 시스템
- ftz
- 재귀
- 스택
- 큐
- 미로 탐색 알고리즘
- c언어
- C
- Stack
- 정렬 알고리즘
- HTML
- level13
- SWiFT
- 시간복잡도
- Java
- Today
- Total
나의 기록, 현진록
[Swift] ARC(Automatic Reference Counting) 본문
ARC
Swift에서 메모리를 추적하고 관리하기 위한 메모리 관리 방식이다. Reference Counting으로 메모리 할당과 해제 여부를 결정한다. 참조 타입은 메모리에서 Heap 영역에 할당 되고 그 인스턴스에 대한 Reference Count를 가진다. 자동으로 관리된다고 하지만 순환 참조(Circular Reference)가 발생하지 않도록 하기 위해서는 개발자의 추가적인 비용?이 필요하다.
ARC / GC
JAVA에서의 GC(Garbage Collection)과 비슷하지만 분명한 차이는 Reference Counting 시점이다.
GC는 런타임 중에 동적으로 메모리를 추적한다. 이러한 경우 프로그램이 실행 중인 동안 계속해서 메모리를 감시하기 때문에 이에 따른 부하가 발생한다. 그러나 불필요한 메모리가 해제될 가능성이 높다는 장단점이 있다. 또 정확히 어느 시점에 인스턴스가 메모리에서 해제되는지 개발자가 예측하기 어렵다.
ARC는 컴파일 시점에 메모리가 해제되는 시점을 결정한다. 이러한 덕분에 실행 중 성능 저하는 발생하지 않고 개발자가 메모리에가 해제되는 시점을 이해하기 쉽다. 그러나 순환 참조가 발생할 경우 메모리가 영원히 해제되지 않은 문제가 생기게 되어 메모리 관리에 대한 개발자의 역량이 필요하게 된다.
ARC의 메모리 관리 방식
ARC는 메모리가 더 이상 필요하지 않다고 판단될 때 메모리에서 자동으로 해제한다. 그렇다면 어떻게 판단할까?
ARC는 생성된 인스턴스에 대해 얼마나 많은 프로퍼티, 상수, 변수 등이 이 인스턴스를 참조하고 있는지 추적한다. 어디선가 참조가 이루어진다면 Reference Count를 1씩 증가시키고 참조가 끝나면 1씩 감소시킨다. 이 때 해당 인스턴스의 Reference Count가 0이 되면 더 이상 인스턴스에 대한 참조가 이루어지지 않기 때문에 할당된 메모리를 해제시킨다.
인스턴스가 필요한 동안 해제 되지 않도록 ARC는 프로퍼티, 상수, 변수, 클래스 인스턴스 등에 강한 참조(Strong Reference)를 만든다. 해당 인스턴스를 유지하고 강한 참조가 남아있는 한 할당 해제를 허용하지 않기 때문에 강한 참조라고 부른다.
Reference Counting 동작
class Person{
let name: String
init(name: String){
self.name = name
print("\(name) is being initialized")
}
deinit{
print("\(name) is being deinitialized")
}
}
Person이 초기화되고 해제될 때 메시지를 출력한다.
var reference1: Person?
var reference2: Person?
var reference3: Person?
옵셔널 타입의 Person의 인스턴스를 3개 생성한다. 옵셔널 타입이므로 nil로 초기화 되고 Person 인스턴스를 참조하지 않는다.
reference1 = Person(name: "Ryu Hyeon Jin")
// Ryu Hyeon Jin is being initialized
reference1 변수에 Person의 인스턴스를 할당하였고 강한참조를 가진다. ARC는 메모리를 유지하고 참조가 이루어지는 동안은 해제하지 않는다. Reference Count가 1 증가한다.
reference2 = reference1
reference3 = reference1
// Ryu Hyeon Jin is being initialized
1, 2는 동일한 Persen 인스턴스(reference1)를 참조한다. 강한 참조 2개 추가된다. Reference Count는 2 증가한다.
reference1 = nil
reference2 = nil
// Ryu Hyeon Jin is being initialized
1, 2는 nil을 할당하여 강한 참조가 해제, refercen3의 강한 참조만 남고 인스턴스
Reference Count 2가 감소하여 1이 남았다.
마지막 강한 참조를 해제 시킬 때까지 Person 인스턴스의 Reference Count가 0이 되지 않아 할당을 해제하지 않는다.
reference3 = nil
// Ryu Hyeon Jin is being initialized
// Ryu Hyeon Jin is being deinitialized
마지막 남은 강한 참조를 지우고 더 이상 참조가 이루어지지 않아 ARC는 Person 인스턴스가 메모리에서 필요 없다고 판단하여 할당을 해제 한다.
강한 참조 사이클
class Person{
let name: String
var apartment: Apartment?
init(name: String){
self.name = name
}
deinit{
print("\(name) is being deinitialized")
}
}
class Apartment{
let address: String
var tenant: Person?
init(address: String) {
self.address = address
}
deinit{
print("Apartment \(address) is being deinitialized")
}
}
Person 클래스에는 Apartment 클래스의 옵셔널 타입 변수가 있다. (사람이 어느 아파트에 사는지)
Apartment 클래스에는 Person 클래스의 옵셔널 타입 변수가 있다. (아파트에 누가 사는지)
var hyoenjin: Person?
var unit9: Apartment?
이 변수 모두 옵셔널 타입이기 때문에 초기값은 nil이다.
hyoenjin = Person(name: "hyeonjin")
unit9 = Apartment(address: "109")
각각 강한 참조가 생성된다.
hyoenjin?.apartment = unit9
unit9?.tenant = hyoenjin
Person 인스턴스에서는 Apartment 인스턴스를 참조하고, Apartment 인스턴스에서는 Person 인스턴스를 참조한다.
이 두 인스턴스의 연결은 강한 참조 사이클을 생성하게 된다.
hyeonjin = nil
unit9 = nil
강한 참조 사이클이 형성되어 두 인스턴스 모두 deinit 구문을 호출하지 않는다. 강한 참조 사이클은 Reference Count가 0으로 떨어지지 않아 ARC에 의해 메모리 해제가 되지 않고 메모리 누수를 유발하게 된다.
강한 참조 사이클 해결
Swift는 강한 참조 사이클을 해결하기 위해 약한 참조(weak reference)와 미소유 참조(unowned reference) 2가지 방법을 제공한다.
약한 참조와 미소유 참조 한 인스턴스가 강한 유지 없이 다른 인스턴스를 참조할 수 있고 강한 참조 사이클을 방지한다.
약간의 차이점이 있다면 다른 인스턴스가 먼저 할당이 해제되는 경우일 때 약한 참조를 사용한다. 반대로 다른 인스턴스의 수명이 동일하거나 더 긴 경우 미소유 참조를 사용한다.
약한 참조(weak)로 강한 참조 사이클 해결
약한 참조는 참조하는 인스턴스를 강하게 유지하지 않기 때문에 약한 참조가 유지되는 동안에 해당 인스턴스가 할당 해제되도 문제가 생기지 않는다.
class Person{
let name: String
var apartment: Apartment?
init(name: String){
self.name = name
}
deinit{
print("\(name) is being deinitialized")
}
}
class Apartment{
let address: String
weak var tenant: Person? // weak 키워드
init(address: String) {
self.address = address
}
deinit{
print("Apartment \(address) is being deinitialized")
}
}
hyeonjin = Person(name: "hyeonjin")
unit9 = Apartment(address: "109")
hyeonjin?.apartment = unit9
unit9?.tenant = hyeonjin
hyeonjin = nil
// hyeonjin is being deinitialized
hyeonjin의 apartment는 약한 참조이기 때문에 hyeonjin 변수의 강한 참조만 중단하면 Person 인스턴스에 대한 강한 참조가 없어져 deinit 구문이 호출된다.
미소유 참조(unowned)로 강한 참조 사이클 해결하기
약한 참조와 마찬가지로 미소유 참조 또한 참조하는 인스턴스를 강하게 유지하지 않는다. 그러나 미소유 참조는 다른 인스턴스의 수명이 같거나 더 긴 경우에 사용한다. 약한 참조와 달리 미소유 참조는 항상 값을 갖고 있다고 예상?한다.
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") }
}
이전 Person/Apartment 예제와 코드는 비슷하지만 개념이 조금 다르다. Customer와 CreditCart 간의 관계는 고객이 신용카드를 가지고 있지 않을 수도 있지만 신용카드는 반드시 고객을 가진다는 것이 차이이다.
Customer는 CreditCard 보다 수명이 길기 때문에 CreditCard의 인스턴스가 존재하는 동안에는 Customer 인스턴스가 항상 존재하기 때문에 CreditCard의 customer 변수는 unowned 키워드를 사용한다.
var hj: Customer?
hj = Customer(name: "hyeonjin")
hj?.card = CreditCard(number: 1234_1234_1234_1234, customer: hj!)
// hyeonjin is being deinitialized
// Card #1234123412341234 is being deinitialized
출처
'Programming > Swift' 카테고리의 다른 글
[Swift] 객체 지향 프로그래밍 Object-Oriented Programming, OOP (0) | 2025.02.07 |
---|---|
[Swift] 강한 참조 사이클(클로저) (0) | 2025.02.07 |
[Swift] Struct와 Class의 특징 (0) | 2025.02.05 |
[Swift] 15.2 필터 (0) | 2021.09.14 |
[Swift] 15.1 맵 (0) | 2021.09.14 |