맛동산이

weak , strong, unowned, ARC 는 뭐고 왜 쓰는건가요?? 본문

앱/Swift

weak , strong, unowned, ARC 는 뭐고 왜 쓰는건가요??

진ddang 2023. 3. 21. 22:58

weak strong unowned는 메모리를 참조하는 방식에 대한 이야기다.

해당 이야기를 이해하기 이전에 ARC(autometic reference counting) 에 대해서 알아야한다.

컴파일과 런타임에 대해서

컴파일은 코드가 어셈블리 언어로 해석되는 것을 의미한다.

런타임으 해당 어셈블리 언어가 메모리에 올라가서 실제로 작동하는 것을 의미한다.

ARC(autometic reference counting)

arc는 이전에 objective-c에서 MRC라는 수동적으로 reference counting 을 해주었던것을 자동으로 해주는 프로그램이라고 생각하면 쉽다.

그렇다면 reference counting이 왜 필요하고 왜 어떻게 작동하는것인가에 대한 의문이 생긴다.

참조 횟수 계산 방식 (reference counting)은 메모리를 제어하는 방법 중 하나로, 쓰레기 수집의 한 방식이다. 구성 방식은 단순하다. 어떤 한 동적 단위(객체, Object)가 참조값을 가지고 이 단위 객체가 참조(참조 복사)되면 참조값을 늘리고 참조한 다음 더이상 사용하지 않게 되면 참조값을 줄이면 된다. 보통 참조값이 0이 되면 더이상 유효한 단위 객체로 보지 않아 메모리에서 제거한다.


데이터의 메모리는 4가지 영역이 있다.

  1. code : 실제 프로그램의 코드가 올라가는 영역이며, 해당 영역에서 한줄식 cpu가 코드를 읽게된다.
  2. data : 전역변수, 정적변수가 선언되며, 프로그램 실행시 할당되고 프로그램 종료시 해제된다.
  3. stack : 지역변수과 매개변수가 선언되며, 함수를 호출할때 할당되는 영역이다.
  4. heap : 동적으로 사용자에 의해서 선언되고, 할당되는 영역의 메모리이다.
  • arc에서 중요한 메모리 영역은 heap이다.

위에서 설명한 heap 영역에는 closure과 class등 참조형의 자료가 들어가게 된다.

이때문에 , 개발자가 직접 동적으로 할당하고, 해제 해주어야 한다는 단점이 존재하였는데

이를 objectiveC에서는 직접 해주었다면 swift에서는 이것을 referenc counting이라는 방식을 통해서 직접 할당 해제 해주게 된다.

reference counting

reference countingd의 방식은 다음과 같다.

  1. ARC가 compile time에 직접 코드를 분석하여 필요한 부분에 release, retain등의 함수를 추가해줌
  2. 해당 추가된 함수는 runtime에 실행됨
  3. 해당 rc가 0이되면 메모리에서 해제됨

여기에서 release는 rc를 올리는것으로 해당 스코프에서 객체가 존재하도록 해준다.

retain은 rc를 줄이는것으로 해당 Rc가 0이 되면 메모리에서 해제된다.

strong 참조

자신이 참조하는 인스턴스의 retain count를 증가시킨다. (rc +1)

값 지정 시점에서 retain 되고, 참조가 종료되는 시점에서 release된다.

선언할때 아무것도 적어주지 않는다면 strong이 기본값이다.

reference counting은 상수 또는 변수에 / 클래스 인스턴스를 할당 할 때 / 해당 인스턴스에 대한 강한 참조가 생긴다.

즉 최초에 인스턴스가 생기면 rc는 자동으로 1이 된다는 이야기다.

그렇다면 해당 인스턴스 에서 서로 다른 인스턴스를 부르게 되면 어떻게 될까?

강한순환 참조

이렇게 서로의 클래스에서 서로를 참조하고 있다면, (해당 인스턴스에서 인스턴스를 가지고 있게 되면)

person의 인스턴스의 rc는 2, unit4a의 rc도 2가 된다.

  • 최초에 인스턴스가 생성될때 1, 그리고 서로의 apartment와 tenant 변수에서 참조하고 있기 때문에 2

이러한 상태에서 john의 참조를 끊게 되면, 즉 john에 nil을 넣게 되면 rc는 1이 된다.

이러한 상황에서 unit4a의 참조또한 끊게 되면, 즉 unit4a에 nil을 넣게되면 rc는 또한 1이된다.

하지만 외부에서는 서로의 인스턴스에 대한 접근이 불가능하고, 두 인스턴스는 영원히 메모리에 남게된다.

ARC에서는 rc가 0 이어야 자동으로 메모리에서 해제 해주게 되는데 이러한 과정이 불가능해지는것이다.

이러한 현상을 메모리 누수라고 한다.

이를 해결하기 위해서 사용하는것이 weak 참조와 unowned이다.

weak 참조

weak참조는 할때 rc를 1로 올리지 않는다.

즉, 인스턴스가 생성되어도 0의 rc를 가지게 된다.

Apple : 다른 인스턴스의 수명이 더 짧은 경우 즉, 다른 인스턴스를 먼저 할당 해제 할 수있는 경우 약한 참조를 사용한다.

john이라는 객체가 생성될때 rc는 1이되었지만, tenant의 변수에서 person을 weak참조 하였기 때문에 rc는 여전히 1이다.

따라서 john이 nil이되면 rc는 0이 되고 메모리에서 해제가 된다.

  • 이때 rc가 0이 된 변수나 객체를 nil로 만들게 된다.

중요한점은

: 런타임에 값이 nil로 변경될 수 있으므로 항상 Optional로 선언되어야함.

: 런타임에 값이 nil로 변경될 수 있으므로 항상 var로 선언되어야함.

이러한 점이다.

 

아까와 같은 상황에서 john이 nil이 되면, tenant 변수는 참조를 잃게되고 nil값이 된다.

즉 런타임상황에 nil값이 될수 있으므로 optional, 그리고 값이 변할수 있기 때문에 var로 선언되어야 하는것이다.

unowned 참조

unowned또한 weak참조와 동일하게 rc를 늘리지 않는다.

하지만 rc가 0 이 되었다고 해도 nil값으로 변경하지는 않는다.

하지만 해당 인스턴스 자체는 존재하지 않게 되는것이다.

빈 공간을 포인팅 하고 있게 되는데 이러한 포인터를 dangling pointer라고 한다.

이렇게 빈 포인터에 접근하려고 하면 crush가 난다.

Apple : 다른 인스턴스의 수명이 동일하거나 수명이 더 긴 경우 unowned(미소유) 참조를 사용한다.

안전하지 않은 경우와 같은 tenant 의 경우에는 john과 unit4A가 둘다 strong으로 인스턴스가 생성되었을때

unit4a의 tenant가, person을 unowned로 참조되었을 경우

john이 nil이 되어도 tenant는 person인스턴스를 가르키고 있다. (nil이 되지않음, 하지만 빈 값, 이를 dangling poin라고한다. )

이때 unit4A.tenant를 접근하면 crush가 난다.

 

unowned특징

  • unowned는 nil값이 들어가지 않기 때문에 let으로 선언이 가능하다.
  • unowned는 nil값이 들어가지 않기 때문에 optional 값으로 설정할수 없다.
  • 참조하고 있던 값이 없어져도, nil값으로 변경되지 않기 때문에 기존에 참조하고 있던값이 먼저 해제되면 안된다.

참고자료

Swift ) ARC / Strong Reference Cycle 해결 방법(weak, unowned)

갓제드님… 사랑합니다.

반응형