프로그램이 실행되면 운영체제는 메모리에 이 프로그램을 위한 공간을 할당한다. 이때, 그 공간은 Code
, Data
, Heap
, Stack
으로 나뉘어져 있다.
heap
에선 데이터의 크기가 확실하지 않을 때 사용하는 영역이다. 즉, 메모리 크기에 대한 제한이 없으며, 본질적인 범위가 전역이기에 프로그램의 모든 함수에서 액세스할 수 있다. 주로 class
객체나 closure
같은 참조 타입들이 할당된다. 또한, swift
에서는 참조 계산을 통한 메모리 해제를 ARC
에서 한다.
heap
은 스레드들이 공유하는 영역이기도 하기에 thread-safety
하지 않는다. 이때, thread-safety
는 멀티 스레드 프로그래밍에서 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음을 뜻한다. 즉, heap
는 스레드가 안전하지 않기에 lock
과 같은 자원이 필요하고, 이것이 오버헤드로 이어진다. lock
은 여러 스레드 간에 자원을 접근하는 매커니즘을 제공하는 것이며, 오버헤드는 어떤 처리를 하기 위해 들어가는 간접적인 처리 시간, 메모리 등을 말한다.
Stack
은 CPU가 스택 메모리를 효율적으로 구성하기에 속도가 매우 빠르며, 메모리를 직접 해제하지 않아도 된다. 그러나 지역 변수만 액세스 가능하다는 단점이 있고 메모리 크기에 대한 제한이 있으며, 자신의 스택 영역을 초과한 경우 Stack Overflow
가 발생할 수 있다.
Heap
와 Stack
을 나누어 설명하긴 했지만, 사실 둘은 같은 메모리 영역을 공유한다. 같은 메모리 공간이지만 Heap
영역은 낮은 메모리 주소부터 할당받는 것이고, Stack
영역은 높은 메모리 주소부터 할당받는다. 따라서 Heap
또한 자신의 영역 외로 확장하려다 Heap Overflow
가 발생할 수 있다.
어떠한 클래스를 생성하고, 해당 클래스의 인스턴스 생성 및 초기화를 지역 변수로 했다고 가정한다. 그렇다면 지역 변수는 Stack
에 할당되고, 실제 클래스 인스턴스는 Heap
에 할당된다. 또한, Stack
영역에 있는 지역 변수가 Heap
영역의 인스턴스를 참조하고 있는 형태이기에 지역 변수 안에는 힙에 할당된 인스턴스의 주소값이 들어가 있다.
이후 Stack
에 있는 지역 변수가 함수 종료 시점에 사라지고 나면 Heap
에 남은 인스턴스는 직접 메모리 해제를 해야한다. 이때, iOS
에서는 ARC
가 해제를 해준다.
Swift
공식 문서에서는 ARC
에 대해 클래스 인스턴스가 더 이상 필요하지 않을 때 메모리를 자동으로 해제한다고 설명한다. ARC
는 컴파일 시점에 언제 참조되고 해제되는지 정해지며, 런타임 때 그대로 실행되어 추가 리소스가 발생하지 않는다. 그렇기에 개발자가 참조 해제 시점을 파악할 수 있다. 그러나 순환 참조 발생 시 영구적으로 메모리가 해제되지 않을 수 있다.
ARC
는 Automatic Reference Counting
의 약자로 자동 참조 계수라고 한다. 말 그대로 메모리의 참조 횟수(RC)를 계산하여, 참조 횟수가 0이 되면 더 이상 사용하지 않는 메모리라 판단하고 해제한다. 다시 말해 RC
는 인스턴스의 참조 횟수를 숫자로 나타낸 것이다. 인스턴스의 주소값을 변수에 할당할 때 참조 횟수가 증가하며, 인스턴스를 가리키던 변수가 메모리에서 해제되거나 변수에 다른 값을 대입한 경우 참조 횟수가 감소한다.
Strong
는 강한 참조이다. 해당 인스턴스의 소유권을 가지며, 인스턴스의 주소값이 변수에 할당될 때, RC
가 증가하면 강한 참조가 된다. ARC
는 선언할 때 아무것도 적어주지 않으면 default
로 Strong
이 된다. 인스턴스의 강한 참조가 남아있는 한 메모리를 해제하지 않고 모든 강한 참조들이 없어질 때 메모리를 해제한다.
Strong
의 단점으로는 순환 참조
가 발생할 수 있다. 순환 참조는 두 개의 객체가 서로가 서로를 참조하고 있는 형태를 말한다.
Weak
는 해당 인스턴스의 소유권을 가지지 않고, 주소값만을 가지고 있는 포인터 개념이다. 인스턴스를 참조할 시, RC
를 증가시키지 않는다. 또한, 참조하던 인스턴스가 메모리에서 해제된 경우 자동으로 nil
이 할당되어 메모리가 해제된다. 따라서 Weak
는 무조건 Optional Type
의 변수여야 한다.
Unowned
는 해당 인스턴스의 소유권을 가지지 않고, 자신이 참조하는 인스턴스의 RC
를 증가시키지 않는다. 그러나 Weak
와 다르게 Unowned
는 참조하던 인스턴스가 메모리에서 해제된 경우,nil
을 할당받지 못하고 해제된 메모리 주소값을 계속 들고 있다. 즉, 인스턴스를 참조하는 도중에 해당 인스턴스가 메모리에서 사라질 일이 없다고 확신하는 것이 Unowned
이다.
Unowned
으로 선언된 변수가 가리키던 인스턴스가 메모리에서 먼저 해제된 경우, 접근하려고 하면 에러 발생한다. 그렇기에 웬만해선 Weak
를 사용하는 것을 권장한다.
Unowned
는 nil
을 할당받지 못하기에 Non-Optional Type
으로 선언해야 했지만, Swift 5.0
부터는 Optional Type
으로도 선언이 가능하다.