용어 설명
RF: reference couning, 객체가 몇개의 참조자를 참고하고 있는지의 갯수
ARC: Automatic Refercence Countion 컴파일러가 자동으로 RF를 추적하는 시스템
메모리 누수: memory leak 사용한 메모리를 헤제하지 않고 계속 쌓이거나 동작하는 현상 성능에 문제를 일으킬 수 있다
메모리 관리란?
프로그램이 실행되면서 사용되는 메모리 공간을 효율적으로 관리하는 것을 말한다.
즉 메모리를 효율적으로 관리 할 수 있도록 해주는 것.(메모리 누수 방지)
swift는 objc와 다르게 컴파일러가 자동적으로 RF(reference couning) 메모리 관리를 도와주고 있는데,이를 ARC(Automatic Refercence Countion)이라고 한다.
ARC(Automatic Refercence Countion)
ARC는 객체가 참고하는 변수나 상수를 추적하여 관리한다.
참조하는 객체가 늘어나면 같이 RF도 늘어나고 감소하면 같이 감소한다.
ARC의 규칙은 다음과 같다
- 변수나 상수가 새로운 객체를 참고하면 +1
- 변수나 상수가 새로운 객체를 참고하는 것을 그만두면 -1
- 참조 횟수가 0이면 메모리에서 해제
따라서 개발자가 매번 메모리 관리를 위해 노력할 필요는 없지만 언제나 그렇듯 예외적인 경우가 있다 🤪
예외인 경우를 제외하면 ARC로 인해 자동적으로 메모리 관리를 해준다.
예외적인 경우 , 강한참조 사이클
강한참조 사이클이란 메서드가 아래 코드와 같이 서로를 참조하는 상황을 말한다.
강한참조 사이클은 메모리 누수 현상을 발생시킨다.
class Brand {
var brandName: String
var item: Item? //Item참조
init(brandName: String) {
self.brandName = brandName
}
deinit {
print("\(brandName) 메모리 해제")
}
}
class Item {
var skirt: String
var brandName: Brand? //Brand 참조
init(skirt: String) {
self.skirt = skirt
}
deinit {
print("\(skirt) 메모리 해제")
}
}
var item1: Brand? = Brand(brandName: "CHANEL")
var skirt: Item? = Item(skirt: "A LINE SKIRT")
item1?.item = skirt
skirt?.brandName = item1
item1 = nil //서로 참조중, 따라서 RC값은 2 - 1 = 1
skirt = nil //서로 참조중, 따라서 RC값은 2 - 1 = 1
print("RC값 1이 남음") //RC값 1이 남음
위 코드에서 nil을 할당했음에도 deinit의 대한 값은 출력되지 않는다 RC값이 1이 남아서 이다.
즉 필요 없는데도 메모리에는 할당된 상태로 위같은 상황이 지속된다면 우리의 소중한 앱이 메모리 잡아먹는 괴물로 변할 수 있다는 말이다.
이런 무시무시한 문제를 어떻게 해결 할 수 있을까?
해결방법
메모리 누수를 막을 수 있는 2가지 해결방법이 있다.
1. weak키워드
말그대로 약하게 만들어주는 키워드라고 생각하면 편하다.
귀찮게 하지말고 nil로 만들면 메모리에서 방빼 라는 키워드로 생각하면 편하다.
nil로 초기화 된다는건 다음과 같은 제약사항이 있다는 말과 동일하다.
- weak키워드는 객체가 메모리에서 해제되면 자동으로 nil로 초기화 시킨다.
- optional타입으로 선언된 변수에만 가능
- 상수(let은 불가능)
class Brand {
var brandName: String
weak var item: Item?
init(brandName: String) {
self.brandName = brandName
}
deinit {
print("\(brandName) 메모리 해제")
}
}
class Item {
var skirt: String
weak var brandName: Brand?
init(skirt: String) {
self.skirt = skirt
}
deinit {
print("\(skirt) 메모리 해제")
}
}
var item1: Brand? = Brand(brandName: "CHANEL")
var skirt: Item? = Item(skirt: "A LINE SKIRT")
item1?.item = skirt
skirt?.brandName = item1
item1 = nil //CHANEL 메모리 해제
skirt = nil //A LINE SKIRT 메모리 해제
print("RC값 0이 남음") //RC값 0이 남음
//weak를 추가함으로서, deinit 값이 같이 출력된다.
2. unowned
unowned는 자동으로 nil값으로 변경되지 않는다 따라서, nil값이 아니라는 것이 보장되어야 한다
nil값으로 변경되지 않는다는 것은 다음과 같은 제약사항이 있다는 것을 말한다
- optional타입으로도 선언 가능(swift 5 이후로 가능)
optional타입으로 선언이 가능하지만 nil로 자동할당은 안됨 - 상수(let도) 사용 가능
- 약간의 성능향상을 불러올 수 있다.(참조 대상이 메모리에서 사라지지 않는다는 것 즉, 다시말해 nil값이 아니라는 것을 보장하기 때문)
class Brand {
var brandName: String
unowned var item: Item?
init(brandName: String) {
self.brandName = brandName
}
deinit {
print("\(brandName) 메모리 해제")
}
}
class Item {
var skirt: String
weak var brandName: Brand?
init(skirt: String) {
self.skirt = skirt
}
deinit {
print("\(skirt) 메모리 해제")
}
}
var item1: Brand? = Brand(brandName: "CHANEL")
var skirt: Item? = Item(skirt: "A LINE SKIRT")
item1?.item = skirt
skirt?.brandName = item1
item1 = nil //A LINE SKIRT 메모리 해제, RC값 0
skirt = nil //CHANEL 메모리 해제, RC값 0
print("RC값 0")
'🍎swift' 카테고리의 다른 글
[Swift] 연산자 커스텀 타입 (0) | 2023.04.05 |
---|---|
Swift - 문자열 다루기 01 (0) | 2023.03.29 |
swift - 제네릭(Generics) (0) | 2023.03.22 |
고차함수 (0) | 2023.03.07 |
swift 클로저 01 - Closure사용 이유와 형태 (0) | 2023.02.27 |