[LEVEL 1] 19 - ARC #36
Open
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
close #24
19-ARC
1. ARC(Automatic Reference Counting)의 동작 원리는 무엇인가요?
ARC는 Swift에서 객체의 메모리를 관리하는 시스템입니다. ARC는 객체가 더 이상 필요하지 않게 될 때 자동으로 메모리를 해제하여 메모리 누수를 방지하고, 수동으로 메모리를 해제해야 하는 번거로움을 줄여줍니다. ARC의 동작 원리는 객체의 참조 카운트(reference count)를 기반으로 다음과 같은 방식으로 동작합니다.
참조 카운트 관리
ARC 동작 과정 예시
이처럼 ARC는 참조 카운트를 관리하며, 객체가 더 이상 필요하지 않게 되면 자동으로 메모리를 해제하여 효율적으로 메모리를 관리합니다. ARC 덕분에 Swift에서 메모리 관리가 간편해졌고, 명시적인 메모리 해제 코드가 필요하지 않게 되었습니다.
ARC와 GC
ARC와 GC 모두 객체의 생명 주기를 관리하기 위한 방법입니다. 생명 주기를 관리한다는 것은 한 객체가 메모리에서 얼마나 살아있는지를 추적하는 것입니다. 이를 추적함으로써 더 이상 필요하지 않은 객체는 메모리에서 해제시킬 수 있습니다.
ARC
ARC란 기존에 수동으로 개발자가 직접 retain/release를 통해 reference counting을 관리해야하는 부분을 자동으로 관리해주는 기술입니다. 컴파일 타임에 컴파일러가 객체의 참조 횟수를 추적하고, 필요에 따라 자동으로 객체를 release하는 코드를 실행 파일에 주입합니다.
장점:
단점:
가비지 컬렉션(Garbage Collection)
GC는 런타임에 동작하며, 백그라운드에서 사용되지 않는 객체 및 객체 그래프를 관리합니다.
GC는 불확실한 간격으로 발생하므로, 객체가 더 이상 사용되지 않는 정확한 순간에 반드시 해제 되는 것은 아닙니다.
장점:
단점:
순환 참조에 따른 ARC와 GC의 처리 방식
순환 참조는 두 개(또는 그 이상)의 객체가 서로를 참조할 때 발생합니다. 객체에 대한 외부 참조가 해제 되어도, 서로를 참조하고 있어 객체가 살아있는(alive) 상태를 유지하는 현상을 말합니다.
GC는 reachable 객체를 살펴 보며 동작합니다. 외부 참조가 존재하지 않는 것을 감지하면, 서로를 참조하는 객체 그래프 전체를 버립니다. 따라서 순환 참조 문제가 발생하지 않습니다.
ARC는 더 낮은 수준에서 작동하고, 참조 수를 기반으로 생명 주기를 관리하기 때문에 순환 참조를 자동으로 처리할 수 없으며, 결과적으로 메모리 누수 문제가 발생합니다.
ARC는 순환 참조를 피하는 방법을 제공하지만, 개발자의 명시적인 설계가 필요합니다. 이를 위해 ARC는 strong, weak, unowned와 같은 Storage Modifier를 도입한 것입니다.
런타임에 수행되는 GC는 항상 메모리를 차지하고 감시해야하기 때문에 메모리 사용량이 더 늘어날 수 밖에 없으며, 지속적인 감시를 위해 CPU를 일부 사용해야만합니다. 반면에 ARC는 컴파일러가 메모리 반환 코드를 삽입해주는 것이기에 오버헤드에서 비교적 자유롭다는 특징이 있습니다. 이는 특히 메모리와 CPU가 데스크탑에 비해 제한적인 모바일 기기에서는 더 중요한 문제이고 그만큼 성능 측면에서 이점입니다.
2. Retain Cycle이 발생하지 않도록 방지하는 방법은 무엇인가요?
약한 참조 (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은 다음과 같은 상황에서 유용하게 사용됩니다.