From 8897857786dfa1cb28d7aa9d9779d980cff05c47 Mon Sep 17 00:00:00 2001 From: Daehoon Lee Date: Mon, 6 Jan 2025 21:30:09 +0900 Subject: [PATCH] Create 19-ARC.md --- LEVEL 1/19-ARC.md | 112 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 LEVEL 1/19-ARC.md diff --git a/LEVEL 1/19-ARC.md b/LEVEL 1/19-ARC.md new file mode 100644 index 0000000..5ddc2ef --- /dev/null +++ b/LEVEL 1/19-ARC.md @@ -0,0 +1,112 @@ +## 19-ARC + +### 1. ARC(Automatic Reference Counting)의 동작 원리는 무엇인가요? + +ARC는 Swift에서 객체의 메모리를 관리하는 시스템입니다. ARC는 객체가 더 이상 필요하지 않게 될 때 자동으로 메모리를 해제하여 메모리 누수를 방지하고, 수동으로 메모리를 해제해야 하는 번거로움을 줄여줍니다. ARC의 동작 원리는 객체의 참조 카운트(reference count)를 기반으로 다음과 같은 방식으로 동작합니다. + +참조 카운트 관리 + + - ARC는 객체에 대한 강한 참조(strong reference)를 추적하여 객체가 얼마나 많이 참조되고 있는지 관리합니다. + - 각 객체는 참조될 때마다 참조 카운트를 증가하고, 참조가 해제되면 참조 카운트가 감소합니다. + - 참조 카운트가 0이 되면 객체가 더 이상 필요하지 않다고 판단하여 메모리에서 해제됩니다. + +#### ARC 동작 과정 예시 + + - 객체가 생성될 때, 참조 카운트는 1로 설정됩니다. + - 다른 객체가 강한 참조를 통해 이를 참조하면 참조 카운트가 증가합니다. + - 강한 참조가 해제되면 참조 카운트가 감소합니다. + - 참조 카운트가 0이 되면, deinit 메서드가 호출되고 객체가 메모리에서 해제됩니다. + +이처럼 ARC는 참조 카운트를 관리하며, 객체가 더 이상 필요하지 않게 되면 자동으로 메모리를 해제하여 효율적으로 메모리를 관리합니다. ARC 덕분에 Swift에서 메모리 관리가 간편해졌고, 명시적인 메모리 해제 코드가 필요하지 않게 되었습니다. + +#### ARC와 GC + +ARC와 GC 모두 객체의 생명 주기를 관리하기 위한 방법입니다. 생명 주기를 관리한다는 것은 한 객체가 메모리에서 얼마나 살아있는지를 추적하는 것입니다. 이를 추적함으로써 더 이상 필요하지 않은 객체는 메모리에서 해제시킬 수 있습니다. + +#### ARC + +ARC란 기존에 수동으로 개발자가 직접 retain/release를 통해 reference counting을 관리해야하는 부분을 자동으로 관리해주는 기술입니다. 컴파일 타임에 컴파일러가 객체의 참조 횟수를 추적하고, 필요에 따라 자동으로 객체를 release하는 코드를 실행 파일에 주입합니다. + +장점: + + - 컴파일 타임의 작업이며, 컴파일 시점에 언제 참조되고 해제되는지 결정됩니다. + - 객체가 사용되지 않을 때, 실시간으로 메모리에서 release합니다. + - 백그라운드 처리가 없으므로, 모바일 디바이스와 같은 저전력 시스템에 더 효과적입니다. + - 런타임 시점에 추가적인 오버헤드가 발생하지 않습니다. + +단점: + + - 순환 참조(retain cycle)를 해결할 수 없습니다. + - 즉, 메모리 누수의 위험이 있습니다. + +#### 가비지 컬렉션(Garbage Collection) + +GC는 런타임에 동작하며, 백그라운드에서 사용되지 않는 객체 및 객체 그래프를 관리합니다. +GC는 불확실한 간격으로 발생하므로, 객체가 더 이상 사용되지 않는 정확한 순간에 반드시 해제 되는 것은 아닙니다. + +장점: + + - 순환 참조(retain cycle)를 포함하여 전체 객체 그래프를 관리할 수 있습니다. + - 런타임의 작업이며 주기적으로 참조를 추적하여 사용하지 않는 객체를 해제합니다. + - 객체가 해제될 확률이 ARC에 비해 높습니다. + +단점: + + - 백그라운드에서 수행되므로, 객체의 정확한 release 시간을 알 수 없습니다. + - GC가 발생하는 겨우, application의 다른 스레드가 일시적으로 멈출 수 있습니다. + - 런타임 시점에 객체를 추적하는 과정에서 오버헤드가 발생하며, 이로인한 성능저하가 있을 수 있습니다. + +#### 순환 참조에 따른 ARC와 GC의 처리 방식 + +순환 참조는 두 개(또는 그 이상)의 객체가 서로를 참조할 때 발생합니다. 객체에 대한 외부 참조가 해제 되어도, 서로를 참조하고 있어 객체가 살아있는(alive) 상태를 유지하는 현상을 말합니다. + +GC는 reachable 객체를 살펴 보며 동작합니다. 외부 참조가 존재하지 않는 것을 감지하면, 서로를 참조하는 객체 그래프 전체를 버립니다. 따라서 순환 참조 문제가 발생하지 않습니다. + +ARC는 더 낮은 수준에서 작동하고, 참조 수를 기반으로 생명 주기를 관리하기 때문에 순환 참조를 자동으로 처리할 수 없으며, 결과적으로 메모리 누수 문제가 발생합니다. + +ARC는 순환 참조를 피하는 방법을 제공하지만, 개발자의 명시적인 설계가 필요합니다. 이를 위해 ARC는 strong, weak, unowned와 같은 Storage Modifier를 도입한 것입니다. + +런타임에 수행되는 GC는 항상 메모리를 차지하고 감시해야하기 때문에 메모리 사용량이 더 늘어날 수 밖에 없으며, 지속적인 감시를 위해 CPU를 일부 사용해야만합니다. 반면에 ARC는 컴파일러가 메모리 반환 코드를 삽입해주는 것이기에 오버헤드에서 비교적 자유롭다는 특징이 있습니다. 이는 특히 메모리와 CPU가 데스크탑에 비해 제한적인 모바일 기기에서는 더 중요한 문제이고 그만큼 성능 측면에서 이점입니다. + +### 2. Retain Cycle이 발생하지 않도록 방지하는 방법은 무엇인가요? + +- 순환 참조(Retain Cycle)는 두 객체가 서로 강한 참조를 할 경우 발생하며, 참조 카운트가 0이 되지 않으므로 메모리에서 해제되지 않는 문제를 야기합니다. +- 대부분의 경우, 객체는 강한 참조를 통해 다른 객체를 참조하며, 이때 참조 카운트가 증가합니다. +- 순환 참조 문제를 방지하기 위해 약한 참조(weak reference)와 비소유 참조(unowned reference)를 사용하여 ARC의 참조 카운트를 조정할 수 있습니다. + +#### 약한 참조 (Weak Reference) + +약한 참조는 참조하고 있는 객체가 메모리에서 해제되면 자동으로 nil로 설정됩니다. ARC는 약한 참조의 경우 참조 카운트를 증가시키지 않기 때문에 순환 참조를 방지할 수 있습니다. + +#### 비소유 참조 (Unowned Reference) + +비소유 참조는 참조하고 있는 객체가 항상 메모리에 있다고 가정할 수 있을 때 사용합니다. 즉, 대상 객체의 생명주기가 현재 객체의 생명주기보다 길거나 같다는 가정이 있을 때 사용하며, 약한 참조와 마찬가지로 참조 카운트를 증가시키지 않습니다. + +비소유 참조 대상 객체가 존재한다는 가정에서 사용하기 때문에 객체가 해제되었을 때도 nil이 아닌 값을 유지하므로 강제로 해제된 객체를 참조하면 런타임 오류가 발생할 수 있습니다. + +#### 사이드 테이블 + +weak를 사용하는 경우 사이드 테이블을 생성합니다. 즉, 사이드 테이블을 위한 추가 비용이 발생합니다. 하지만 unowned의 경우 사이드 테이블을 사용하지 않습니다. + +약한 참조는 객체가 메모리에서 해제(deallocation) 된다면 사이드 테이블이 객체를 nil로 할당받기에 null safety 하다는 장점이 있지만 처음 약한 참조로 접근시에 사이드 테이블 생성 및 할당, 객체를 사이드 테이블을 통해 접근해야 한다는 점, 객체를 nil로 할당하는 zeroing weak 과정을 진행해야 한다는 점 등으로 인해 속도라는 성능 측면에서 손해를 볼 수 있습니다. + +미소유 참조는 객체 자체를 바로 참조하기에 dangling pointer가 되었을 때는 치명적인 오류를 발생시킬 수 있지만 직접 참조 방식이기 때문에 속도가 약한 참조에 비해 빠르고 메모리 사용량을 줄일 수 있다는 장점이 있습니다. + +#### Closure에서 Capture List 작성 + +클로저 내부에 객체를 캡쳐하는 경우 순환 참조가 발생할 수 있습니다. 클로저와 클래스 모두 참조 타입이기 때문에 발생합니다. + +기본적으로 클로저 표현식은 해당 값의 강한 참조를 사용하여 주변 범위에서 상수와 변수를 캡쳐합니다. 하지만 캡쳐 리스트를 사용하면 클로저에서 값이 캡쳐되는 방식을 명시적으로 변경 가능합니다. + +캡처 리스트의 항목은 클로저가 생성될 때 값을 복사하여 초기화됩니다. 즉, 캡처 리스트의 각 항목에 대해 상수는 주변 범위에서 이름이 같은 상수 또는 변수로 초기화됩니다. + +클로저에서 순환 참조를 해결하기 위해서는 캡쳐 리스트에 weak와 unowned 키워드를 사용하여 클로저 내부에서 객체를 참조하는 방식을 변경합니다. + +### 3. deinit 메서드는 언제 호출되며, 어떤 역할을 하나요? + +deinit 메서드는 객체가 메모리에서 해제될 때 오버라이드 없이 자동으로 호출되는 소멸자이며, 파라미터나 반환값을 가질 수 없습니다. +Swift에서는 객체가 해제되기 직전에 deinit 메서드가 자동으로 호출되며, deinit은 다음과 같은 상황에서 유용하게 사용됩니다. + +1. 리소스 해제: 파일 핸들, 네트워크 연결, 데이터베이스 연결과 같은 리소스를 해제하는 데 사용됩니다. +2. Observer 제거: NotificationCenter나 KVO에서 추가된 옵저버를 제거하여 메모리 누수를 방지합니다. +3. 디버깅: 객체의 해제 여부를 확인하는 로그를 추가하여 순환 참조가 발생하는지 확인하는 데 유용합니다.