iOS 메모리에 관하여

시루봉로·2023년 3월 18일
0

iOS, 스위프트 정리

목록 보기
2/3
post-thumbnail

메모리 구조 기초

프로그램이 실행되면 운영체제는 메모리에 이 프로그램을 위한 공간을 할당한다. 이때, 그 공간은 Code, Data, Heap, Stack으로 나뉘어져 있다.

Heap

heap에선 데이터의 크기가 확실하지 않을 때 사용하는 영역이다. 즉, 메모리 크기에 대한 제한이 없으며, 본질적인 범위가 전역이기에 프로그램의 모든 함수에서 액세스할 수 있다. 주로 class 객체나 closure 같은 참조 타입들이 할당된다. 또한, swift에서는 참조 계산을 통한 메모리 해제를 ARC에서 한다.

heap은 스레드들이 공유하는 영역이기도 하기에 thread-safety하지 않는다. 이때, thread-safety는 멀티 스레드 프로그래밍에서 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음을 뜻한다. 즉, heap는 스레드가 안전하지 않기에 lock과 같은 자원이 필요하고, 이것이 오버헤드로 이어진다. lock은 여러 스레드 간에 자원을 접근하는 매커니즘을 제공하는 것이며, 오버헤드는 어떤 처리를 하기 위해 들어가는 간접적인 처리 시간, 메모리 등을 말한다.

Stack

Stack은 CPU가 스택 메모리를 효율적으로 구성하기에 속도가 매우 빠르며, 메모리를 직접 해제하지 않아도 된다. 그러나 지역 변수만 액세스 가능하다는 단점이 있고 메모리 크기에 대한 제한이 있으며, 자신의 스택 영역을 초과한 경우 Stack Overflow가 발생할 수 있다.

힙과 스택의 메모리 관계

HeapStack을 나누어 설명하긴 했지만, 사실 둘은 같은 메모리 영역을 공유한다. 같은 메모리 공간이지만 Heap 영역은 낮은 메모리 주소부터 할당받는 것이고, Stack 영역은 높은 메모리 주소부터 할당받는다. 따라서 Heap 또한 자신의 영역 외로 확장하려다 Heap Overflow가 발생할 수 있다.

참조(Reference) 타입과 Heap

어떠한 클래스를 생성하고, 해당 클래스의 인스턴스 생성 및 초기화를 지역 변수로 했다고 가정한다. 그렇다면 지역 변수는 Stack에 할당되고, 실제 클래스 인스턴스는 Heap에 할당된다. 또한, Stack 영역에 있는 지역 변수가 Heap 영역의 인스턴스를 참조하고 있는 형태이기에 지역 변수 안에는 힙에 할당된 인스턴스의 주소값이 들어가 있다.

이후 Stack에 있는 지역 변수가 함수 종료 시점에 사라지고 나면 Heap에 남은 인스턴스는 직접 메모리 해제를 해야한다. 이때, iOS에서는 ARC가 해제를 해준다.

ARC란

Swift 공식 문서에서는 ARC에 대해 클래스 인스턴스가 더 이상 필요하지 않을 때 메모리를 자동으로 해제한다고 설명한다. ARC는 컴파일 시점에 언제 참조되고 해제되는지 정해지며, 런타임 때 그대로 실행되어 추가 리소스가 발생하지 않는다. 그렇기에 개발자가 참조 해제 시점을 파악할 수 있다. 그러나 순환 참조 발생 시 영구적으로 메모리가 해제되지 않을 수 있다.

ARCAutomatic Reference Counting의 약자로 자동 참조 계수라고 한다. 말 그대로 메모리의 참조 횟수(RC)를 계산하여, 참조 횟수가 0이 되면 더 이상 사용하지 않는 메모리라 판단하고 해제한다. 다시 말해 RC는 인스턴스의 참조 횟수를 숫자로 나타낸 것이다. 인스턴스의 주소값을 변수에 할당할 때 참조 횟수가 증가하며, 인스턴스를 가리키던 변수가 메모리에서 해제되거나 변수에 다른 값을 대입한 경우 참조 횟수가 감소한다.

Strong, 강한 참조

Strong는 강한 참조이다. 해당 인스턴스의 소유권을 가지며, 인스턴스의 주소값이 변수에 할당될 때, RC가 증가하면 강한 참조가 된다. ARC는 선언할 때 아무것도 적어주지 않으면 defaultStrong이 된다. 인스턴스의 강한 참조가 남아있는 한 메모리를 해제하지 않고 모든 강한 참조들이 없어질 때 메모리를 해제한다.

Strong의 단점으로는 순환 참조가 발생할 수 있다. 순환 참조는 두 개의 객체가 서로가 서로를 참조하고 있는 형태를 말한다.

Weak, 약한 참조

Weak는 해당 인스턴스의 소유권을 가지지 않고, 주소값만을 가지고 있는 포인터 개념이다. 인스턴스를 참조할 시, RC를 증가시키지 않는다. 또한, 참조하던 인스턴스가 메모리에서 해제된 경우 자동으로 nil이 할당되어 메모리가 해제된다. 따라서 Weak는 무조건 Optional Type의 변수여야 한다.

Unowned, 미소유 참조

Unowned는 해당 인스턴스의 소유권을 가지지 않고, 자신이 참조하는 인스턴스의 RC를 증가시키지 않는다. 그러나 Weak와 다르게 Unowned는 참조하던 인스턴스가 메모리에서 해제된 경우,nil을 할당받지 못하고 해제된 메모리 주소값을 계속 들고 있다. 즉, 인스턴스를 참조하는 도중에 해당 인스턴스가 메모리에서 사라질 일이 없다고 확신하는 것이 Unowned이다.

Unowned으로 선언된 변수가 가리키던 인스턴스가 메모리에서 먼저 해제된 경우, 접근하려고 하면 에러 발생한다. 그렇기에 웬만해선 Weak를 사용하는 것을 권장한다.

Unownednil을 할당받지 못하기에 Non-Optional Type으로 선언해야 했지만, Swift 5.0부터는 Optional Type으로도 선언이 가능하다.

profile
안녕하세요.

0개의 댓글