안녕하세요. 린다입니다.
오늘은 면접때 탈탈 털리고 온 ARC를 톺아봅시다..
ARC란.. Auto Reference Counting
- Swift는 RC로 메모리를 관리함 == Reference Count로 관리한다는 의미
- 메모리의 참조횟수를 계산하여, 참조 횟수가 0이되면 더이상 사용하지 않는 메모리라 생각하여 해제함
- 즉, 모든 인스턴스는 자신의 RC 값을 가지고 있음 (인스턴스 생성 시 힙에 같이 저장됨)
이런 ARC 카운팅을 확인할 수 있는 방법이 있는데
바로 CFGetRetainCount 임
→ “Core Foundation 객체”의 참조 횟수를 반환함
https://developer.apple.com/documentation/corefoundation/1521288-cfgetretaincount
간단한 예시로 어떻게 카운팅 하는지 확인해봅시다..
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: "팀쿡")
print(CFGetRetainCount(reference1)) // 2
reference2 = reference1
print(CFGetRetainCount(reference1)) // 3 (reference2의 ARC 추가)
print("referernce2 \(CFGetRetainCount(reference2))") // 3 (reference1 ARC + 1을 바라보고 있는 2의 ARC)
reference3 = reference1
print(CFGetRetainCount(reference1)) // 4 (reference3 의 ARC 추가)
print("referernce2 \(CFGetRetainCount(reference2))") // 4 ( 1 + 2 + 3 )
print("referernce3 \(CFGetRetainCount(reference3))") // 4 ( 1 + 2 + 3 )
CFGetRetainCount를 사용해서 ARC를 확인해보자
처음 Reference1의 CFGetRetainCount이 2인 이유는.. CFGetRetainCount가 ARC를 확인하기 위하여
Ref1를 참조하기 있기 때문에 +1이 되는 것 (CFGetRetainCount는 처음 한번만 추가됨)
그래서 최종 Ref1의 ARC는 3인 (CFGetRetainCount꺼 - 1) 확인할 수 있음
현재 Ref1, 2, 3은 모두 동일한 Heap영역의 주소를 바라보고 있다는 것을 의미함
그림으로 확인해보면.. 이런 양상!
여기서 이렇게 해보면?
reference1?.name = "나나" //
reference1 = nil
print(reference2?.name) // Optional("나나")
print("referernce2 after 1 nil \(CFGetRetainCount(reference2))") // referernce2 3
Person에 대한 reference1의 참조가 없어져서 -1 인 3이 되는것을 확인할 수 있음
사실 여기서 살짝 헷갈린 부분이 있었는데
나는 ref1에 Nil을 대입했는데 어떻게 ref2의 name이 출력되는거지? 두개는 주소값을 공유했는데??
싶었는데.. ref1 == nil 의 의미는 참조를 해제 이기 때문에 ref2.name 은 나나 가 맞음.
그래서 모두 메모리를 해제하고 name을 출력해보면 nil이 출력됨
reference1?.name = "나나"
reference1 = nil
reference2 = nil
reference3 = nil
print(reference2?.name) // nil
deinit도 출력됨 - 실제 출력문
+ nil은 우리가 Person?으로 인스턴스를 생성했기 때문에 가능한것
(사실 이 부분이.. 또 클로저와 ARC와 큰연관성이 있지만.. 추후에 정리한다면 링크 붙여볼게요)
나나 is being deinitialized
nil
그럼 이렇게 됐을 때.. 어떻게 nil이 나오는 것인가..? 또 nil은 어떤것이길래 출력이 되는가?
nil에 대한 상태를 지피티와 함께 공부해보았어요.. (틀린게 있을 수 있으니 있으면 말씀 부탁드립니다.)
class Person {
var name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var reference1: Person?
이렇게 옵셔널 Person 타입의 reference1 변수가 있을 때 지금 ref1의 메모리 상태는 어떤것일까?
그렇다면
1. reference1은 스택에 올라가 있지만 Person에 대한 인스턴스를 가르키고 있지 않아서 Person의 ARC값은 0인가요?
- ㅇㅇ.
- reference1은 스택에 존재하지만, Person 인스턴스를 가리키고 있지 않으므로 해당 인스턴스의 ARC 값은 0
2. reference1 은 스택에 올라가 있지만, 주소값을 가지지 않고 어떤 값도 가지고 있지않은 변수인가요?
- ㅇㅇ.
- reference1은 스택에 존재하지만, nil 값을 가지고 있으므로 어떤 값을도 가리키지 않는 변수
그림으로 확인해보면 ... 대충 ?
이렇게 각각 올라가만 있는 상태 같슴니다.
그니까 값이 없는거를 의미한다고 생각하면 될거 같아요
메모리 공간에는 올라가 있는데 그 안은 비어있지만, 올라가는 있는 껍데기 같은거(?)
밑은 공부하면서 지피티한테 혹시나? 싶어서 물어본 내용
그렇다면.. 그냥 일반적인 값 타입들을 옵셔널로 선언하고 nil을 대입한다면? (feat, GPT..)
-> 참조타입이랑 똑같은 양상인데 Heap, Stack만 다른 것
1. let test: Int? = nil 이라는 코드가 있을 때, test 프로퍼티는 어디에 적재되어 있는것인가요?
- let test: Int? = nil은 옵셔널 타입인 Int?에 nil 값을 할당하는 것입니다. 이 코드에서 test는 메모리에서 사용 가능한 공간이 할당되어 있지만, 그 공간 안에는 실제로 값이 존재하지 않습니다. 이렇게 nil 값이 할당된 옵셔널은 스택에 적재되어 있습니다. 스택은 변수 및 상수와 같은 프로그램의 실행 컨텍스트를 관리하는 데 사용되는 메모리 영역입니다.
2. nil의 작동 원리를 자세하게 설명해주세요.
- nil은 옵셔널 타입의 특별한 값으로, "값이 없음"을 나타냅니다. Swift에서 옵셔널은 값이 없을 수 있는 경우를 처리하기 위한 타입이며, 이를 통해 안전하게 코드를 작성할 수 있습니다. nil은 모든 타입의 옵셔널에 할당될 수 있습니다. nil을 할당하면 해당 옵셔널은 어떤 값을 갖지 않음을 나타내며, 이는 다른 값과 구분됩니다. 따라서 nil을 사용하여 옵셔널을 초기화하면 해당 옵셔널은 값이 없음을 나타내는 것이며, 해당 변수나 상수는 이를 처리하기 위해 프로그램의 흐름을 변경할 수 있습니다.
다시 원문으로 돌아가서..
그러면 더 복잡하게 만들어서 확인해봅시다.
만약에.. 구조체 안에서 참조타입의 변수를 만든다면?
struct People {
let linda = Person(name: "린다")
let jade = Person(name: "제이드")
}
let people = People()
print("people ARC \(CFGetRetainCount(people))") // 에러 발생
print("linda ARC \(CFGetRetainCount(people.linda))") // 2
print("jade ARC \(CFGetRetainCount(people.jade))") // 2
구조체 인스턴스의 people의 ARC를 확인해보려고 하면 에러가 발생합니다.
" Argument type 'People' expected to be an instance of a class or class-constrained type "
왜냐? 당연함. 왜냐면 위에서 Core Foundation 객체의 참조 횟수를 반환한다고 했기 때문에..
하지만 해당 인스턴스 안에 참조 타입 프로퍼티의 ARC를 확인해본다면?
각각 2가 나오는것을 확인해볼 수 있음
구조체가 스택에 쌓이니까 그 안의 클래스들도 스택에 쌓이겠다~ 가 아니라
구조체는 구조체고, 클래스는 클래스라고 생각해야 함
linda, jade는 구조체의 일부가 아니라, 해당 구조체의 인스턴스 내부에 포함된 상수 프로퍼티를 의미함!!
그림으로 보면.. 아마도..
이런 느낌이지 않을까.. 생각합니다!
linda, jade 각각이 heap으로부터 오는 주소값을 가지게 되며, 그렇게 생성된
두개의 변수들을 people이 감싸고(?) 있는 것...
그래서 people은 구조체, 값 타입이기 때문에 별도의 주소값을 갖는게 아님!
그럼 이번에는 클래스를 중복으로 하면 어떻게 될까 ?
class Person {
var name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
struct People {
let linda = Person(name: "린다")
let jade = Person(name: "제이드")
}
class Group {
let student = Person(name: "학생1")
let company = People()
deinit {
print("\(student) 종료!")
print("\(company) 퇴근!")
}
}
var group: Group? = Group()
print("🔥 Group student ARC \(CFGetRetainCount(group?.student))") // 2
print("🔥 Group ARC \(CFGetRetainCount(group))") // 2
클래스 임에도 불구하고 그룹자체는 2라고 출력되는데 그룹안의 company는 안 됨
위에서처럼 클래스는 클래스, 구조체는 구조체라고 생각해야 하는게 맞는거 같습니다.
그래서 그러면.. 대체 그룹의 company는 어디에 있는거지? 싶어서.. 확인해봤습니다.
🚀 group 객체 할당자 Optional(<CFAllocator 0x1f4375da8 [0x1f4375da8]>{info = 0x198696dc8})
🚀 student 객체 할당자 Optional(<CFAllocator 0x1f4375da8 [0x1f4375da8]>{info = 0x198696dc8})
(CFGetAllocator로는 객체 할당자를 확인할 수 있음)
group, student의 객체 할당자는 모두 동일한 값이 나오는것을 확인할 수 있지만,
company의 객체 할당자는 모호하다고 합니다.
그림으로 그려보면..
왜 그런지 조금은 감이 잡히는거 같져?
group의 company는 스택에 있는 people이라는 값 타입을 바라보고 있는데
실상 그 값 타입 안의 변수들은 클래스 인스턴스라서 모호하게? 잡히는 것 같아요.
그래서 결론적으로 Group의 ARC는
group 안의 student 인스턴스만 있기 때문에 2가 출력되는 것 같아요.
(CFGetRetainCount + 1 해서 2임)
다음 게시물에는 위에서 말했던 클로저와 ARC, 즉 강한참조와 약한참조를 정리해보도록 할게요
일단 .. 오늘은 여기까지.. 스택과 힙이 이렇게 쌓인다는 것을 알아두고
사실 조금 찜찜하니 나중에 추가로 좋은 레퍼런스를 발견하거나
알게 된다면 추가로 정리해보도록 하겠습니다.
끝!
'Swift > Swift 기초문법' 카테고리의 다른 글
Swift 기초 문법, 함수와 클로저를 톺아보자 ... 2번째 (1) | 2024.02.06 |
---|---|
Swift 기초 문법, 함수와 클로저를 톺아보자 (1) | 2024.02.06 |
DispatchQueue(GCD)를 알아보자2, GCD 사용시 주의해야할 사항 (0) | 2023.11.08 |
DispatchQueue(GCD)를 알아보자, 이제 동기/비동기, 동시/직렬을 곁들인.. (2) | 2023.11.03 |
[Swift 기초문법] Error Handling을 알아보자 (0) | 2023.07.17 |