ARC를 곁들인 순환참조 (1)
혹은 순환참조를 곁들인 ARC…
ARC (Automatic Reference Counting)
Swift에서 앱의 메모리를 관리하는 방법
간단하게는 자동으로 메모리를 관리해주는 녀석이라고 할 수 있다.
여기서 ‘메모리’ 란?
우선 여기서 메모리는 Heap을 의미한다.
Swift에서 인스턴스나 클로저 같은 참조(Referene) 타입들은 자동으로 힙에 할당이 된다.
그런데 Heap은 반드시 사용 후에 메모리 해제를 해 줘야 하는데.. 우리는 한 기억이 없다
→ 진짜 그런가? 진짜 메모리 해제를 꼭 해 줘야 하나? 안해주면 어떻게 되나?
→ 메모리 누수 (필요없는 메모리 사용량 증가, 앱이 죽을 수 있음)
어쨌든 우리가 하지 않아도 되는 이유는 ARC가 대신 해 주고 있기 때문!
필요 없는 메모리를 자동 해제해서 관리해 주는 게 ARC가 하는 일이다.
Heap의 메모리 관리
Heap의 메모리를 관리하는 방법에는 두 가지가 있다.
첫번째는 GC(Garbage Collection), 두 번째는 RC(Reference Counting).
GC는 run-time
에 참조를 추적하고 RC는 compile-time
에 참조를 추적한다는 것이 가장 큰 차이점이다.
ARC 에서 쓰이는 RC는 말 그대로 Reference Counting, 즉 참조의 수를 계산해서 0이 되면 메모리를 해제시키는 방법이다. 그런데 만약 어떠한 이유로 Reference가 0이 되지 않는 경우에는 영구적으로 메모리 해제가 되지 못할 수 있다는 단점이 있다.
이 경우는 여러 인스턴스가 서로 강한 참조를 하는, 사이클이 생성된 경우를 의미하고 이것을 강한 순환 참조라고 한다. 오늘 알아볼 것이 이것!
강한 참조
서로 참조를 하는 것은 어느정도 이해가 가겠는데, 강한 참조는 무엇일까?
그래서 먼저 Swift의 참조 방식을 알아보자아
Swift의 참조 방식
strong : 강한 참조
- 참조하는 인스턴스의 Reference Count를 증가시킨다
→ 인스턴스의 소유권을 가짐
- 인스턴스 참조의 default(기본) 방식이다
- 강한 참조가 생기는 경우: 상수 또는 변수에 클래스 인스턴스를 할당할 때
공식 문서에 나오는 아래 예시를 보자.

여기서 클래스 인스턴스를 할당하면 기본 방식인 강한 참조가 발생한다.

강한 참조는 RC를 증가시키기 때문에 각 인스턴스의 RC는 1이 된다.

이 때, 아파트와 거주자에 서로를 (인스턴스를) 할당해 주면 또 다시 강한 참조가 발생하게 된다. 그리고 RC는 2로 늘어나게 된다.

이 때, john과 unit4A를 nil
로 만들어 주면서 강한참조가 하나씩 사라지지만, 여전히 인스턴스들이 서로를 참조하고 있기 때문에 RC=1로 유지되어 메모리에서 해제되지 않는 상태가 된다.
이 때 인스턴스를 nil
로 만들어 주고 싶어도 이미 john과 unit4A와의 연결이 끊어졌기 때문에 방법이 없다.

이것이 바로 강한 참조 사이클!
weak : 약한 참조
- 참조하는 인스턴스의 Reference Count를 증가시키지 않는다
- 더 수명이 짧은 인스턴스에 대하여 약한 참조를 사용한다.
예) 아파트는 있고 거주자는 사라짐, 나는 있고 자동차는 바뀜
- 참조하는 인스턴스가 해제되면 자동으로
weak
참조를nil
로 설정한다.
예) Audi R8이 폐차되면 나의 car는 nil
이 된다.
결과적으로 런타임에 값이 nil
로 변경될 수 있으니 그래서 규칙들이 있다.
- 항상
Optional
로 선언되어야 한다 - 항상
var
로 선언되어야 한다
아까 사용했던 예시를 살짝 바꿔 보자. 이제는 거주자가 weak var
로 선언되었다.

아까와 같이 강한 참조가 발생하고, 이번에는 거주자가 weak
로 선언되었기에 약한 참조가 발생한다. 그래서 Person 인스턴스의 RC는 1, Apartment 인스턴스의 RC는 2가 된다.

이 때 john을 nil
로 만들면서 RC가 0이 되어 Person 인스턴스는 메모리에서 사라집니다.
여기서 중요한 점은 weak
참조하던 인스턴스가 해제되면서 자동으로 nil
로 설정되었다는 것!

그리고 마지막으로 unit4A를 nil
로 만들면서 Apartment 인스턴스도 성공적으로 메모리를 떠나게 됩니다..!!

unowned : 미소유 참조
- 참조하는 인스턴스의 Reference Count를 증가시키지 않는다
weak
와 반대로 수명이 동일하거나 더 긴 인스턴스에 대하여unowned
를 사용한다.
예) 고객과 신용카드 객체가 있을 때, 고객은 신용카드가 없을 수 있지만 신용카드는 고객이 없을 수 없다. 이 때 신용카드 객체에서 고객을 선언할 때 unowned
를 사용하면 좋다.
- 또
weak
와 차이점은nil
로 만들어 주지 않는다 (항상 값이 있다고 예상)
단점) 그래서 여전히 포인터가 해제된 메모리 영역을 가리키고, 접근하려 하면 당연히 에러 뜬다 → Crash 발생의 우려가 있다는 문제
따라서 unowned
의 특징은 아래와 같다.
- 항상 값이 있다고 예상하기 때문에
Optional
일 필요 없음 (옵셔널도 가능은 함) let
으로 선언한다
이번에는 위에서 언급한 Customer와 CreditCard의 예시가 사용됐다.
신용카드보다 당연히? 수명이 긴 소유주를 unowned let
으로 선언했다.

이번에는 john의 인스턴스를 생성하고, 그 안의 card의 인스턴스를 만들어 주었다.
unowned
는 RC를 증가시키지 않기에 RC는 1로 동일.

이 때, john을 nil
로 설정하게 된다면 Customer 인스턴스는 사라지고, 따라서 CreditCard 의 RC도 0이 되어 메모리에서 해제되게 된다. 이 때 weak
와 다르게 customer
의 인스턴스가 자동으로 nil이 되지 않는다.

여기까지 순환참조에 대해 알아봤다..
원래는 클로저에서의 weak self
를 왜 써야하는 지 탐구하는 목적이었는데.. 다음 편에서 이어서 해야 할 것 같다 !
다음 이야기
https://mila00a.tistory.com/64
클로저에서의 weak self — 순환참조 (2)
https://mila00a.tistory.com/63 ARC를 곁들인 순환참조 (1) 혹은 순환참조를 곁들인 ARC… ARC (Automatic Reference Counting) Swift에서 앱의 메모리를 관리하는 방법 간단하게는 자동으로 메모리를 관리해주는 녀석
mila00a.tistory.com