ARC (자동 참조 카운트) 자세히 알아보기 + 참조 키워드(weak, strong, unowned)

728x90

 

 

정의

ARC(Automatic Reference Counting)는 메모리 구조에서 힙 영역을 관리한다.
Swift에서 힙에 메모리를 할때는 참조타입을 생성할 때 자동으로 할당하게 된다.
ARC는 할당된 메모리가 더 이상 필요하지 않을 때 자동으로 해제한다.

C++에서는 new, delete 키워드를 통해 클래스 인스턴스를 생성할 때 메모리를 할당, 해제 한다.

특징

컴파일 시점에 언제 참조되고 해제되는지 결정되어 런타임때 그대로 실행된다.

장단점

  • 장점
    • 개발자가 참조 해제 시점을 파악할 수 있음
    • RunTime 시점에 추가 리소스가 발생하지 않음
  • 단점
    • 순환 참조 발생 시 영구적으로 메모리가 해제되지 않을 수 있음

MRC? ARC?

둘 다 Reference Counting을 하지만, Manual, Automatic의 차이가 있다.
MRC는 2011년 이전 Objective-C에서 사용했던 방법이다.
C++와 비슷하게 retain, release 를 사용해 할당, 해제를 한다.
Obj-C 코드를 건드릴 일이 있다면... 참고

동작 방식

메모리의 참조 횟수를 계산해 참조 횟수가 0이 되면 더 이상 사용하지 않는 메모리라 생각해 해제한다.
참조 횟수를 계산하는 방법을 알아보자

참조 횟수가 증가할 때

  1. 인스턴스를 새로 생성할 때
  2. 기존 인스턴스를 다른 변수에 대입할 때

참조 횟수가 감소할 때

  1. 인스턴스가 가리키던 변수가 메모리에서 해제되었을 때
  2. nil이 지정되었을 때
  3. 변수에 다른 값을 대입한 경우
  4. 프로퍼티의 경우 속해있는 클래스 인스턴스가 메모리에서 해제될 때
    • A클래스 안에 B 클래스 인스턴스가 프로퍼티로 존재할때 각각 인스턴스의 RC가 증가함
    • A 인스턴스를 생성 후 제거 했을 때 A의 프로퍼티인 B 인스턴스도 A 해제 후 해제.

순환 참조(retain cycle) & keywords

ARC 단점 중 순환참조 발생 시 영구적으로 메모리가 해제되지 않을 수 있다. 라는게 있었다.
순환 참조는

두 개의 객체가 서로를 참조하고 있는 형태다.


먼저 코드로 이해해보자

class A { var b: B?}
class B { var a: A?}
// 여기서 A, B의 RC 각각 + 1
var a: A = A()
var b: B = B()
// 여기서 A, B의 RC 각각 + 1
a.b = b
b.a = a
// 최종 A,B의 RC는 각각 2

이렇게 되면 a, b변수를 nil로 만들어 해제하든 뭘하든 RC는 1 남게된다.
그렇게 memory leak이 발생하게 된다.

이런 문제를 해결하기 위해서는 키워드 중 weak, unowned를 사용하면 된다.

strong (강한 참조)

Swift를 사용하면서 인스턴스의 주소값이 변수에 할당될 때 강한참조를 사용한것이다.
인스턴스 생성시 default로 strong 키워드가 있어 RC를 +1 하게 된다.

하지만 강한 참조를 사용하다보면

순환참조

가 발생해 문제를 일으킬 수 있다.

weak (약한 참조)

단어 그대로 strong의 반대인 약한 참조다
strong은 default라 사용안했지만 weak의 경우 명시해줘야 한다.
특징을 먼저 알아보고 간단하게 코드를 확인해보자

특징

  1. 인스턴스 참조시 RC를 증가시키지 않음
  2. 참조하던 인스턴스가 메모리에서 해제된 경우, 자동으로 nil이 할당돼 메모리가 해제된다.
  3. 위와 같은 이유로 무조건 Optional 타입의 변수다.

해제되는 과정

class A { weak var b: B?}
class B { var a: A?}
// 여기서 A, B의 RC 각각 + 1
var a: A = A()
var b: B = B()
// A의 RC = 2, B의 RC = 1
a.b = b
b.a = a
// 순환이 끊어져 
  1. 생성시 A, B 각각 +1
  2. 각 인스턴스에 프로퍼티를 할당할 때도 +1 이지만 weak이 붙은 프로퍼티인 B는 증가하지 않음
  3. 메모리 해제시 각 RC를 -1하게 됨
  4. B의 RC는 해제 직전 1이 였기에 0이되고 메모리에서 제거됨
  5. A는 B가 메모리에서 제거됐기 때문에 B가 가지고 있던 A인스턴스가 해제되고 A의 최종 RC도 0이 된다.
    생각보다 간단하다.

weak을 어디다 선언해도 문제 없을까?
둘 중 수명이 더 짧은 인스턴스를 가리키는 애를 약한 참조로 선언하면 된다.
아니면 그냥 둘 다 weak으로 하고 예외처리 하거나..

unowned (미소유 참조)

weak과 동일한 특징을 갖는다. (마지막 특징 빼고..) 특별한 점

옵셔널이 아니다


인스턴스를 참조하는 도중에 해당 인스턴스가 메모리에서 사라질 일이 없다고 확신하는것이다.

만약에 인스턴스가 메모리에서 해제된 경우에도 unowned는 nil을 할당받지 못하고 계속 해제된 메모리의 주소를 갖고있게 된다.

그래서 위와 같은 경우에서는 런타임 에러가 생긴다.

웬만해서 weak을 사용하는걸 권장한다

Swift 5.0 부터 Optional Type으로도 선언 가능하다.

추가

Grabage Collection과의 차이

  1. GC는 런타임에서 ARC는 컴파일 타임에서!
    1. GC는 런타임에서 동작하다보니 추가 리소스가 필요해 성능 저하가 발생할 수 있다.
  2. RC에 비해 GC가 인스턴스 해제될 확률이 높음
728x90