Swift 문서를 읽어보는 시간. 이번에는 ARC와 관련된 부분을 읽어본다.
문서는 여기 ⬇️
https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html
Automatic Reference Counting — The Swift Programming Language (Swift 5.7)
Automatic Reference Counting Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. In most cases, this means that memory management “just works” in Swift, and you don’t need to think about memory management your
docs.swift.org
소개
ARC. Automatic Reference Counting이라고 불리는 이 친구는 Swift가 우리가 만드는 앱의 메모리 사용을 추적하고 관리하기 위해 해 사용한다. 몇몇 상황을 제외하고는 Swift가 자동으로 ARC를 사용해 더 이상 필요하지 않은(사용되지 않는) 클래스 인스턴스의 메모리를 확보하기 때문에 우리가 직접 신경을 쓰지 않아도 된다.
그렇기 때문에 더중요하게 생각해야 하는 부분은 위에 말한 몇몇 상황이다. Swift가 ARC로 메모리를 관리할 때 '이걸 메모리에서 해제 해야할까 말아야 할까?'를 고민하게 만드는 상황들. 이런 부분에서는 개발자가 코드로 어떤 상황인지, 어떤 메모리가 먼저 해제되어야 하는지를 조금 더 자세히 알려줄 필요가 있다.
이름에 Reference라는 단어가 들어가는 것에서 알 수 있듯이 ARC는 클래스의 인스턴스에만 적용이 된다. struct와 enum은 value type이기 때문에 적용되지 않는다.
How ARC Works
ARC가 동작하는 방법은 아주 간단하다.
- 클래스의 인스턴스를 생성하면 ARC는 메모리에 이 인스턴스를 할당할 자리를 만들어준다.
- 인스턴스가 더 이상 필요하지 않은 경우 ARC는 메모리를 다시 회수해 다른 용도로 사용한다.
이렇게 크게 두 가지 동작으로 나눠볼 수 있는데, 만약 내가 지금 사용하고 있는 인스턴스인데 ARC가 정신이 나가서 이걸 확 해제해버린다면..?! 나는 당연히 인스턴스가 있을 거라고 생각했다가 낭패를 볼 수 있다. (Welcome to crash world...^^)
혹시라도 위와 같은 일이 발생하면 안되므로 ARC는 인스턴스가 어디서 참조되고 있는지, 몇 군데나 참조되고 있는지를 관리한다. 그것이 바로 Reference Count. 클래스의 인스턴스가 상수 또는 변수에 할당될 때마다 ARC는 인스턴스에 대한 Reference Count를 1씩 증가시키고, 더 이상 사용하지 않으면 다시 Reference Count를 1씩 감소시킨다. 그래서 Reference Count가 0이 되면 ARC가 메모리를 회수해 가는 것! (이때 Reference Count를 증가시키는 것은 strong으로 생각하면 된다. strong, weak, unowned 중의 strong.)
ARC in Action
공식 문서에 있는 예제를 가져와보자
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
인스턴스가 생성될 때 init 프린트, 해제될 때 deinit 프린트가 되어 있는 간단한 Person 클래스이다.
여기에 reference1, 2, 3이라는 프로퍼티를 선언하고 reference1에 새로운 instance를 할당한다. 아래와 같이 initialized 프린트문이 출력된다.
그 다음으로 reference2와 3에 reference1을 할당해 주면 reference1에 할당해 준 Person 인스턴스의 reference count는 어떻게 될까?
화살표로 가리킨 버튼(Memory Graph)을 눌러 메모리 구조를 확인해보면 아래와 같이 나오는 것을 확인할 수 있다. Person 하나에 Owning References가 3이고, 할당된 변수인 reference1, 2, 3이 보인다.
이제 차례대로 각 변수에 nil을 할당하면서 메모리 그래프를 확인해보자.
각각의 변수가 nil이 될 때마다 own references가 1씩 감소하고, 마지막으로 reference3이 nil이 되면서 Person 인스턴스가 메모리에서 해제되는 것을 확인할 수 있다. (deinit 프린트)
Strong Reference Cycle Between Class Instance
위에서 간단하게 본 예제는 하나의 인스턴스를 가지고 확인했지만 만약, 두 개의 인스턴스가 서로를 strong 하게 잡고 있다면 어떻게 될까? 이 역시고 공식 문서에 있는 예제 코드로 확인해 보자.
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
Person과 Apartment 두 개의 클래스가 있고, 각각의 내부 프로퍼티로 서로를 가질 수 있도록 코드가 작성되어 있다. 우선 john과 unit4A라는 변수에 Person과 Apartment 인스턴스를 각각 할당하고, john과 unit4A의 프로퍼티에 각각 unit4A와 john을 할당해 주었다.
그림으로 보면 각 인스턴스의 내부 프로퍼티가 서로 strong 하게 참조되고 있는 것을 확실히 알 수 있다. 메모리 그래프로 보면...
Person의 apartment가 참조하는건 Apartment 인스턴스인데 Apartment 인스턴스의 tenant는 Person 인스턴스를 참조중이고, 또 그 Person의 apartment는 Apartment...
Apartment와 Person이 무한히 반복되는 굴레에 빠진 것처럼 나온다. (이것이.. 순환참조..?!)
이런 상태에서 john과 unit4A의 apartment와 tenant를 nil로 만들지 않은 상태에서 그냥 nil 처리를 해주면 어떻게 될까? 각 클래스 내부의 deinit이 호출될까?
deinit이 호출되지 않았다! 그 말인즉슨 각 인스턴스의 reference count가 0이 되지 않았다는 것인데, 메모리 그래프로 보면 이렇게 생겼다.
그림으로 보면 john과 unit4A는 Person과 Apartment 인스턴스와 연결이 끊어졌지만 아직 두 인스턴스 사이의 strong 한 참조는 사라진 것이 아니므로 인스턴스가 해제되지 않은 것이다. 인스턴스에 접근할 변수가 nil이 된 마당에 이 인스턴스들에 접근할 수 있는 방법이 있을까? 이 두 인스턴스는 메모리의 바다에서 ㅁ ㅣ아가 되어버렸다..!!
그럼 이런 사태를 어떻게 방지하느냐, 그것은 다음 섹션에서 설명해주고 있다. 그리고 자연스럽게 다음 포스팅으로...
'Swift' 카테고리의 다른 글
ARC(Automatic Reference Counting) - Strong Reference Cycles for Closures (0) | 2023.02.16 |
---|---|
ARC(Automatic Reference Counting) - 강한 순환 참조와 Weak, Unowned (0) | 2023.02.16 |
Self vs self (0) | 2023.02.03 |
Protocols (0) | 2023.02.03 |
Error Handling (0) | 2023.02.03 |