욤찌의 개발 일기
[Swift] ARC(Auto Reference Counting) (1) 본문
앱 만들기를 하다보면 가끔 내가 너무 swift 지식이 부족한 것 같다고 느끼던 요즘,,
대충 알고 있는 것들을 확실히 알아보자 !!! 하면서 정리를 하고 있음
그래서 오늘은 지난주 강의에서 언급 되었던 ARC에 대해서 공부해보았숨다
공식문서에 정리가 잘 되어있어서 열심히 파보았움
💡ARC (Auto Reference Counting)
클래스의 인스턴스와 클로저와 같은 참조 타입은 모두 메모리 공간의 힙 영역에 자동적으로 할당됨.
힙 영역은 개발자가 메모리를 할당하고 해제함으로써 메모리를 효율적으로 관리해야 하는데
사실 swift에서는 개발자가 직접 힙 영역에 할당하고 해제할 필요가 없음.
왜냐!!!! ARC 시스템이 알아서 해주기 때문 !!!
"잠깐,, 그러면 Reference Counting이 뭔데..?"
지금 누가누가 해당 클래스의 인스턴스를 가르키고 있는(참조하고 있는)가에 대한 카운팅이라고 생각하면 됨!!
"근데 ,, 그래서 ,, 참조 숫자를 세는게 왜 의미가 있는건데..?"
해당 인스턴스를 누군가가 계속 사용하고 있다면 메모리에서 사라지면 안되잖숨?
그런 경우에는 메모리가 아주 꽉 잡고 계속 쓰라고 둬야함!! 쓰고있는데 메모리에서 사라지면 안되겠지!!
근데 만약에 그 인스턴스를 아무도 참조를 안한다고 한다면 ..?
쓰지도 않는 인스턴스를 계속 메모리에 두면 얼마나 쓸모 없이 자리만 차지하고 있는 거겠음..?
다른데도 계속 메모리 써야되는데! 그래서 참조 숫자를 카운팅을 하는것은 메모리를 효율적으로 사용하기 위해서 중요함!
(참조는 기본으로 강한 참조를 하기 때문에 따로 어떤 선언을 해주지 않고 참조하게 된다면
그냥 강한 참조를 한다고 알고 있으면 됨)
Object-C 시절에는 MRC(Manual Reference Counting) 시스템이라서 개발자가 메모리 할당을 하고
참조 숫자를 하나하나 세서 마지막에 메모리를 해제하는 것까지 다 코드를 심어줬어야 했음.
그런데 이 과정이 얼마나,, 복잡하고,, 실수가 많겠음..?ㅜ (특히 나같은 개발자에게는..)
그래서 Swift언어를 도입하고 난 이후에는 무조건 ARC 시스템으로만 작동하게 만들어놨음.
매뉴얼은 하고싶어도 할 수가 없음. 개발자가 직접 참조 카운팅을 하지 않아도 알아서 카운팅을 해주고
할당이 해제되고 나면 알아서 카운팅을 줄여서 인스턴스가 더 이상 필요하지 않을 때 메모리를 자동으로 해제해줌!
그런데 여기서 중요한것은 Reference Counting 시스템임.
즉, 참조 카운팅을 자동적으로 해준다는거지 메모리 관리의 모든 것을 자동으로 해준다는 것이 아님!!
그렇기 때문에 개발자가 강한 참조 사이클과 같이 참조가 해제될 수 없는 코드를 입력하게 되면
참조 숫자가 줄어들 수가 없고 그렇기 때문에 메모리 누수가 일어나게 됨.
💡 How ARC Works?
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
공식문서의 예제가 좋아서 한 번 가져와봤숨다.
Person이라는 class를 만들어 보겠음! name 이라는 저장 프로퍼티가 있고 name 프로퍼티에 값을 주게 되면
그 값에 대한 초기화가 진행되고 있다는 메세지를 출력하는 초기화 구문이 있음.
또 인스턴스에 대한 할당이 해제될 때도 메세지를 출력하는 초기화 해제 구문까쥐~
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1, 2, 3이라는 세 개의 Person에 대한 인스턴스를 만듦.
근데 Person? 이라고 옵셔널로 선언했기 때문에 nil로 초기화되어서 인스턴스에 대한 참조가 일어나지 않음.
그리고 옵셔널로 선언해야 나중에 nil을 할당할 수 있음
reference1 = Person(name: "John Appleseed") // RC = 1
// Prints "John Appleseed is being initialized"
그중 하나의 변수에 초기화를 진행함.
그 후에 실행하게 되면 "John Appleseed is being initialized" 라는 초기화 진행 문구가 출력됨.
Person(name: "John Appleseed") 인스턴스가 reference1 변수에 할당되었기 때문에
이 변수는 해당 인스턴스에 대해 강한 참조를 갖게 되고, 인스턴스의 RC는 1이 된다.
이제 해당 인스턴스는 힙 영역에 저장이 되고 인스턴스에 대한 참조가 계속되면 메모리 해제가 되지 않는다.
reference2 = reference1 // RC = 2
reference3 = reference1 // RC = 3
Person(name: "John Appleseed") 인스턴스를 참조하고 있는 reference1 변수를
reference2, reference3에 각각 할당함
이 할당으로 인한 복사 과정에서 할당 받은 변수들에게
Person(name: "John Appleseed") 메모리 주소를 복사해서 같은 인스턴스를 참조하게 됨!!
그래서 해당 인스턴스의 RC는 3이 되었다.
reference1 = nil
reference2 = nil
reference1과 2에 nil을 할당했다.
이렇게 되면 인스턴스를 더 이상 참조를 하지 않겠다는 의미가 되기 때문에
Person(name: "John Appleseed")의 RC는 1로 변경된다.
그러면 이제 참조를 안하니까 deinit이 실행되는거 아녀? 라고 생각했었는데 아니다!
왜냐면 아직 reference3이 해당 인스턴스를 참조하고 있기 때문에 메모리 해제가 일어나지 않는다.
reference3 = nil
// Prints "John Appleseed is being deinitialized"
reference3에도 nil로 값을 주어서 Person(name: "John Appleseed") 인스턴스에 대한 참조를 모두 해제함!
그러면 이제 해당 인스턴스를 참조하고 있는 변수가 없기 때문에 참조 카운팅은 0이된다.
참조 카운팅이 0이 되어 이제 메모리에 남아있을 이유가 없기 때문에 메모리에서 해제 된다.
그래서 reference3 에 nil을 할당한 후에 실행하게 되면 deinit이 실행된다.
💡Strong Reference Cycles Between Class Instances
위 예시처럼 참조와 해제가 스무뜨하면 얼마나 좋겠음,, 그렇지 못한 경우들이 있음.
서로 다른 두 클래스의 인스턴스가 서로에 대한 강한 참조를 유지하게 되는 경우가 있는데,
이때 각 인스턴스가 다른 인스턴스를 강제로 유지시켜서 참조 카운팅이 계속 유지됨.
이 현상을 강한 참조 사이클(Strong reference cycle)이라고 함.
그렇게 되면 해당 인스턴스를 사용하지 않더라도 메모리 해제가 되지않아서
메모리 누수(Memory Leak)가 발생함.
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
}
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
Person class는 name 저장속성과 옵셔널 Aprtment 저장속성을 가지고 있음.
공식문서에 따르면 모든 사람이 아파트를 가지고 있는건 아니라서 옵셔널로 선언했다고 함 ㅋㅋ
name 값을 받아서 초기화할 수 있음.
Apartment class도 unit이라는 저장속성과 옵셔널 Person 타입으로 선언된 저장속성을 가지고 있음.
모든 아파트에 사람이 살고 있는건 아니라서 옵셔널로 선언했다고 함.
두 클래스 다 deinit 함수로 초기화가 해제될 때 출력되는 초기화 해제 구문도 가지고 있음.
var john: Person?
var unit4A: Apartment?
두 클래스를 가지고 각각의 변수를 만들어 타입으로 할당했지만 옵셔널로 선언했기 때문에
아직은 nil로 초기화가 되어 참조가 일어나지 않음.
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
각각의 변수에 각 타입으로 초기화를 해주어서 클래스 인스턴스를 생성함.
이제 각 클래스 인스턴스에 대한 RC는 1로 변경되었음.
각 클래스에 상대 클래스의 타입으로 선언할 수 있는 저장속성이 있지만,
그 속성에 대한 타입은 옵셔널이고 초기화를 하지 않았기 때문에 아직 서로에 대한 강한참조사이클은 일어나지 않음.
이제 강한 참조 사이클을 갖게 해보겠음.
john 이라는 Person 타입의 변수에 apartment 속성을 초기화하고,
unit4A 라는 Apartment 타입의 변수에 tenant 프로퍼티를 초기화 할거임.
john!.apartment = unit4A
unit4A!.tenant = john
둘 다 옵셔널 타입이기 때문에 옵셔널 체이닝으로 옵셔널 값을 벗겨서 사용해줘야 함. 그래서 “!” 붙임.
이렇게 두 변수를 서로에게 할당하게 되면서
Person 인스턴스와 Apartment 인스턴스가 서로를 직접적으로 참조하게 되었음.
그래서 서로에 대한 강한참조사이클이 일어나게 됨!
그런데 이렇게 되면 문제 생김!ㅜㅜ
만약 john 변수와 unit4A 변수에 nil을 할당해서 변수와 인스턴스간의 참조가 해제되더라도
인스턴스들이 서로에 대한 참조는 해제되지 않기 때문에 RC를 계속 유지하게 됨.
그래서 메모리 해제가 되지 않아 메모리 누수가 발생함.
john = nil
unit4A = nil
그렇다면 이 강한 참조 사이클을 해결하기 위한 방법이 있겠찌???
고것은 다음에 이어서 쓰겠숩니다.. ㅎ
아직 개발공부를 시작한지 얼마 되지 않은 응애입니다 ><
댓글로 많이 알려쥬세요~~!~!
📝 Reference ❤️
https://bbiguduk.gitbook.io/swift/language-guide-1/automatic-reference-counting
자동 참조 카운팅 (Automatic Reference Counting) - Swift
이것을 가능하게 하려면 프로퍼티, 상수, 또는 변수에 클래스 인스턴스를 할당할 때마다 해당 프로퍼티, 상수, 또는 변수는 인스턴스에 강한 참조 (strong reference) 를 만듭니다. 참조는 해당 인스
bbiguduk.gitbook.io
https://babbab2.tistory.com/26
iOS) 메모리 관리 (1/3) - ARC(Automatic Reference Counting)
안녕하세요~~ 소들입니다 👀 오늘은 지난 시간 메모리 구조에 이어 Swift를 사용할 때 메모리 관리가 어떤 식으로 되는지에 대해 공부해볼 거예요 :) ARC 면접 단골 질문이라죠? 깔깔 iOS 개발자라
babbab2.tistory.com
'Swift' 카테고리의 다른 글
[Swift] Any랑 AnyObject 공부했다. 근데 TypeCasting을 곁들인,,(1) (3) | 2023.11.03 |
---|---|
[Swift] 나는 그저 Convenience Initializers 가 알고싶었을 뿐인데,,~ (0) | 2023.11.01 |
[Swift] Class 성능 높이는거,, 그거 어떻게 하는건데,, (1) | 2023.10.31 |
[Swift] ARC(Auto Reference Counting) (2) (0) | 2023.07.17 |
[Swift] value type & reference type (구조체와 클래스) (0) | 2023.07.09 |