ARC는 클래스의 객체에서만 작동한다. 새로운 객체가 생성될때 ARC가 메모리를 할당하고 객체에 대한 정보를 저장한다. ARC는 각 객체에 대해 추적해서 객체가 더 이상 필요하지 않으면 메모리에서 할당을 해제한다. 객체를 다른 변수, 상수, 객체 등에 할당하면 강한참조가 만들어지며 참조횟수가 1씩 증가한고 다른 변수, 상수, 객체 등에서 참조를 중단하면 참조 횟수가 1씩 감소한다. 이때 참조횟수가 0이 되면 ARC가 메모리 할등을 해제한다. 객체의 소멸자가 있으면 해제될때 실행된다.
ARC를 통한 메모리 관리에서는 강한참조 사이클 문제가 발생할 수 있으니 조심해야한다. 강한참조 사이클 문제는 객체가 서로를 강한참조 중일 때 객체의 참조를 중단하면 강한참조가 남아있기 때문에 메모리 누수가 발생한다.
이를 해결하기 위한 방법은 약한참조(weak)와 미소유참조(unowned)가 있다. 약한참조의 경우에는 참조하는 다른 객체의 수명이 자신보다 짧을 때 사용하며, 이때 자신의 수명이 남은 상태에서 다른 객체의 수명이 다해 nil 되므로 약한참조의 변수는 옵셔널로 선언되어야한다. 미소유참조의 경우에는 다른 객체의 수명이 자신과 같거나 길때 사용한다. 다른 객체의 수명이 자신과 같거나 길기 때문에 옵셔널로 선언하지 않아도 된다.
약한 참조의 예시) 아파트와 주민이 있을때, 아파트의 주민이 이사를 갈 수 있기 때문에 주민의 수명(life time)이 짧다고 본다. 그러면 아파트의 주민 변수에 약한참조 키워드를 붙인다. 참조 횟수를 보면 (주민1, 아파트1 -> 주민1, 아파트2 -> 주민0, 아파트2 -> 주민해제,아파트1 -> 아파트0 -> 아파트해제) 순이다.
미소유 참조의 예시) 고객과 카드가 있을때, 고객은 카드를 정지 시킬 수 있기 때문에 고객의 수명이 길다고 본다. 그러면 카드의 고객 변수를 미소유 참조 키워드를 붙인다. 참조 횟수를 보면 (고객1 -> 고객1,카드1 -> 고객0,카드1 -> 고객해제,카드0 -> 카드해제 ) 순이다.
Garbage Collection과의 차이
GC방식은 Garbage Collertor(GC)가 프로그램이 동적 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제하는 것으로 이때 필요없게 된 영역은 어떤한 변수도 가리키지 않게 된 영역을 의미한다.
GC방식의 장점은 유효하지 않은 접근을 막고 메모리 누수가 발생하지 않는다는 것이다. 다만 메모리 누수가 발생하지 않는 것이지 메모리 누적에 의해 메모리가 고갈되는 문제는 남아있다.
단점은 개발자가 객체가 필요없어지는 지점을 정확하게 알아도 추적은 GC가 하기 때문에 비용이 든다는 것이다. 이러한 방식은 GC가 메모리를 해제하는 타이밍이나 점유 시간을 미리 알기 어렵기 때문에 자원 관리 하는데 어려움이 발생하며 예측 불가능하게 프로그램이 정지할 수 있다. 그렇기에 GC방식은 실시간 시스템에 적합하지 않다.
대부분의 GC방식은 포인터 추적 방식을 사용하며, 한개 이상의 변수가 접근한 메모리를 앞으로 사용할 수 있는 메모리로 간주한다.
방법1) 표시하고 쓸기 : 단순한 방식을 사용중인 메모리를 표시하고 나머지 메모리를 쓸어버린다.
방법2) 삼색표시 : 방법1에 흰색(접근불가), 회색(검사중), 검은색(검사완료)로 표시한다. 알고리즘이 시작 될 때 변수가 가리키는 객체를 전부 회색으로 표시하고 나머지는 흰색으로 처리한다. 이후 회색 중 하나를 검은색으로 변경하고 해당 객체와 연결된 변수를 전부 회색으로 처리한다. 이걸 전부 반복하고 흰색을 쓸어버린다.
방법3) 객체 이동 기법 : 위의 방식에 추가된 형태로 쓸어버릴 흰색을 다른 메모리로 옮겨두는 것이다. 다시 필요해지면 여기서 찾아서 쓰면 되기 때문에 효율이 상승한다. 단점은 메모리를 옮겨두기 때문에 포인터 연산이 불가능해진다.
방법4) 세대 단위 판별 : 새롭게 할당된 영역 일수록 금방 해제될 확률이 높다는 관찰에서 시작된 방식으로 할당된 시간에 따라 세대별로 구분해서 메모리에 할당 후, 해당 세대의 메모리가 가득차면 메모리를 정리해서 사용중인 메모리를 오래된 세대로 옮겨 사용하는 것이다. 현대적인 언어들은 이 기법을 사용한다
iOS에서의 메모리 구조와 관리 방식
메무리 구조는 4가지 (Code, Data, Heap, Stack)영역 으로 나뉘어진다.
Code 영역은 작성된 코드가 저장되는 영역으로 컴파일 시 결정되며 읽기만 가능한 형태로 저장된다
Data 영역은 전역변수, static 변수 등의 데이터가 저장되는 영역으로 프로그램 시작 시 영역이 결정되지만, 처음부터 모든 데이터가 로드되지는 않는다. lazy(지연 속성)의 경우 필요한 때에 데이터 영역으로 올려서 사용하는 구조이다.
Heap 영역은 런타임 시에 크기가 결정되며 참조 타입을 할당해주면 이 곳에 저장된다. iOS에서는 ARC방식으로 이곳 메모리를 관리하기 때문에 강한 참조 문제만 신경을 쓰면 나머지는 ARC가 알아서 메모리를 해제해준다.
Stack 영역은 컴파일 시 결정되며 지역변수, 매개변수 리턴 값 등이 저장되어 해당 값들의 사용이 종료되면 메모리에서도 해제된다. 컴파일 타임에 크기가 결정되기에 너무 많은 메모리를 할당하게 되면 오버플로우가 발생해 어플이 죽을 수 있다.
추가적으로 스택과 힙은 사실 같은 메모리 공간을 공유한다. 힙은 낮은 메모리 주소를, 스택은 높은 메모리 주소를 할당받아 사용하는데 힙의 영역을 너무 많이 확장하려고 하면 힙 오버플로우가 발생할 수 있다.