기초 메모리 관련 게시글을 먼저 공부 하시길
Heap에 대해 왜 알아야 하냐??
swift는 Heap에 메모리를 언제 할당 한다??
힙에 할당한다는 말을 이해하기 위해 아래 예시 코드를 확인
class Fruit {
var name: String?
var color: String?
init(name: String?, color: String?){
self.name = name
self.color = color
}
}
let banana = Fruit(name: "Banana", color: "Yellow")
Fruit라는 class가 banana라는 인스턴스를 생성하고 값을 초기화 했음
banana는 스택에 할당 되고 Fruit는 힙에 할당 됨
스택의 banana는 힙 영역의 인스턴스를 참조하는 형태임
따라서 banana안엔 힙에 할당된 Fruit의 인스턴스 주소값이 들어가 있음!!
let clone = banana
를 해주면 인스턴스가 복사 되지 않음!!
같은 Heap 영역의 인스턴스를 가리키고 있음!
여기까지가 Reference의 기본 개념임!!
- Reference란??
참조 타입 (Reference Type)
• 정의: 메모리 주소를 참조하는 타입입니다.
• 예시: class 등.
• 특징: 변수에 값을 할당하거나 함수에 전달할 때 동일한 인스턴스를 참조합니다.
Heap의 특징중 하나인
" 사용하고 난 후 반드시 메모리해제를 해줘야한다! "
스위프트는 ARC를 통해 힙에 할당된 메모리가 더 이상 쓸모 없어지면(참조되지 않으면) 자동으로 해제해주기 때문에 우리가 free, release등으로 해제한적이 없는것 !
메모리 기본 글에서 봤던 이 내용에 대해 제대로 살펴볼 것!
메모리를 해제하기 위해선 release, free 등 방법이 있는데 코드를 작성하며 사용해본 적이 없을것이다
그러면 어떻게? 인스턴스 메모리 해제를 해준적이 없었는데???
힙에 이렇게 남아 쓰이지 않는 인스턴스 메모리는 누가 해제해줌???
이렇게 남아있으면 이게다 memory leak (메모리 누수)가 되는것!!
이런 메모리 누수를 자동으로 해제해 주는게 바로 ARC이다!
우리가 지금껏 힙에 메모리를 자동 할당하며 사용해 왔지만 손수 메모리를 해제해주지 않아도 됐던 이유가 바로 인스턴스가 더 이상 필요 없을 때 메모리를 자동으로 해제해주는 ARC가 있었기 때문이다!
Garbage Collector | Reference Counting | |
---|---|---|
참조 계산 시점 | Run Time ▪ 어플 실행 동안 주기적으로 참조를 추적하여 사용하지 않는 instance를 해제함 | Compile Time ▪ 컴파일 시점에 언제 참조되고 해제되는지 결정되어런 타임 때 그대로 실행됨 |
장점 | ▪ 인스턴스가 해제될 확률이 높음 (RC에 비해) | ▪ 개발자가 참조 해제 시점을 파악할 수 있음 ▪ RunTime 시점에 추가 리소스가 발생하지 않음 |
단점 | ▪ 개발자가 참조 해제 시점을 파악할 수 없음 ▪ RunTime 시점에 계속 추적하는 추가 리소스가 필요하여 성능저하 발생될 수 있음 | ▪ 순환 참조가 발생 시 영구적으로 메모리가 해제되지 않을 수 있음 |
MRC는 힙에 메모리를 직접 할당/해체 해주는 것
Reference Counting 을 이용한 메모리 관리방법!
메모리 참조 횟수(RC)를 계산하여,
참조횟수가 0이 되면 더이상 사용하지 않는 메모리로 생각하여 해제함
인스턴스를 현재 누가 가리키고 있는지 없는지(참조여부)를 숫자로 나타낸것 = RC
따라서 모든 인스턴스는 RC값을 가지고 있음!!
[ 인스턴스가 생성 될 때, 힙에 같이 저장됨 ]
let banana = Fruit(name: "Banana", color: "Yellow")
위에서 살펴본 이 코드는 실행되는 시점에
Fruit는 스택에 할당되고 실제 인스턴스는 힙에 할당됨
banana는 힙에 할당된 인스턴스의 주소값이 들어감!
이렇게 인스턴스를 새로 생성할 때 (새로운 변수 대입시!)
해당 인스턴스에 대한 RC가 증가함
let clone = banana
func makeClone(_ origin: Fruit) {
let clone = origin // ② Instance RC : 2
}
let banana = Fruit(name: "Banana", color: "Yellow") // ① Instance RC : 1
makeClone(banana) // ③ Instance RC : 1
banana가 생성되는 순간 인스턴스의 RC + 1이 됨
makeClone 함수가 실행 되어 banana를 참조하는 clone 변수가 생성되는 순간 인스턴스의 RC + 1이 됨
함수가 종료되어 지역변수 clone이 스택에서 해제되는 순간 인스턴스의 RC -1이 됨
makeClone 함수 끝난 시점에 RC = 1인 이유는?
nil 타입 = optional
var banana : Fruit? = .init(name: "Banana", color: "Yellow") // ① Instance RC : 1
var clone = banana // ② Instance RC : 2
clone = nil // ③ Instance RC : 1
banana = nil // ④ Instance RC : 0 (메모리 해제)
var banana : Fruit? = .init(name: "Banana", color: "Yellow") // ① banana Instance RC : 1
var clone : Fruit? = .init(name: "Banana2", color: "Yellow") //② clone Instance RC : 1
banana = clone // ③ clone Instance RC : 2, banana Instance RC : 0
banana에 clone 값을 대입하면
banan 변수에 저장된 주소값이 바뀜 = 참조 카운터 바뀜
banana의 RC = -1
clone의 RC = +1
banana가 가리키던 인스턴스 RC = 0이 됨으로
ARC에 의해 자동 메모리 해제됨!
class Contacts {
var email: String?
var address: String?
init(email: String?, address: String?) {
self.email = email
self.address = address
}
deinit { print("Contacts Deinit") }
}
class Human {
var name: String?
var age: Int?
var contacts: Contacts? = .init(email: "leejh950417@naver.com", address: "JeonJu")
init(name: String?, age: Int?){
self.name = name
self.age = age
}
deinit { print("Human Deinit") }
}
let jjooee: Human? = .init(name: "JJOOEE", age: 30)
jjooee = nil
Human이라는 클래스 안에 contacts라는 클래스 인스턴스가 프로퍼티로 존재함
따라서 Human 인스턴스 jjooee를 생성하면 jjooee는 물론,
프로퍼티인 contacts 인스턴스도 생성되며 두 인스턴스 각각의 RC가 증가됨
💡 주의 💡
jjooee 가 가리키던 Human의 인스턴스가 메모리에서 해제 된다고
프로퍼티인 contacts가 가리키던 contacts 인스턴스의 메모리도 같이 해제되는것 아님!!
contacts 인스턴스의 RC가 1 감소하는 것 뿐이다!