본문 바로가기

카테고리 없음

[TIL] 24.06.04 TUE

# ARC

arc는 automatic reference counting의 약자로

메모리 공간상 Heap 영역의 메모리를 자동으로 관리해주는 기능이다.

 

reference type의 인스턴스는 heap영역에 저장되는 데 해당 인스턴스가 참조되는 수를 reference count로

reference count가 0가 되면 자동으로 메모리에서 해제되도록 한다.

 

강한 순환 참조과 관련해서,

참조의 방식에는 strong, weak, unowned 총 세가지 방식이 있는데 strong 방식은 reference count를 증가 시킨다.

 

heap 영역의 인스턴스들이 둘이상의 참조를 연쇄적으로 하면서 참조중인 경우 stack영역에서 해당 인스턴스를 더이상 사용하지 않더라도

reference count가 0이 되지 않아 메모리 누수가 발생한다. 이것을 strong reference cycle 강한 순환 참조라고 한다.

 

이 문제를 해결하기 위해서 참조하더라도 reference count를 증가시키지 않는 weak과 unowend 방식으로 참조 할 수 있다.

 

weak 방식과 unowned방식 모두 reference count를 증가시키지 않지만,

weak방식은 옵셔널 변수로 해당 인스턴스의 참조가 끝나면 메모리가 해제되며 nil을 할당한다.

 

unowned방식은 참조받는 대상의 수명주기가 더욱 길 때 사용하며, 암묵적으로 항상 값이 존재한다고 생각한다.

 

unowned방식은 옵셔널 타입이 아니므로 해당 객체에 바로 접근해서 사용할 수 있으며, weak방식과 달리 메모리 해제 시 nil을 할당할 과정이 줄어드므로 퍼포먼스적으로 조금 더 빠르며, 옵셔널을 해제하는 과정이 줄어드는 효과를 가진다.

weak은 null safety하므로 런타임시 조금더 안정적

 

⭐️만약 unwoned가 없다면?

 

object가 weak reference에 의해 참조될 때, swift는 해당 object에 side table 을 할당하는데,

  • object가 side table을 가지지 않으면, strong과 unowned reference는 모두 object에 직접 저장된다.
  • 그러나 만약 object가 side table을 가지면, object 내부엔 side table에 대한 포인터가 저장된다. 그리고 strong, unowned, weak count, 그리고 object에 대한 참조는 모두 side table에 저장된다. (side table에 대한 추가적인 작업을 해야하기 때문에, weak 가 성능이 더 안 좋은 것으로 보인다!)
  • 여기서 중요한게 weak reference는 object 에 대해 직접 참조하는게 아니라, side table을 참조한다. 이게 의미하는 바는, weak reference가 남아있더라도 object가 메모리에서 해제될 수 있다는 소리다.
  • side table은 object가 해제된다고 바로 해제되는 건 아니고, 마지막 weak reference가 남아있을 때 메모리에서 해제된다. 그 이유는 weak reference는 object가 아니라 side table에 대한 참조를 가지고 있기 때문이다.

 

만약 어떤 곳에서도 A를 참조하지 않는다면, 해당 A 인스턴스는 메모리에서 해제된다. 그러나! 해제되기 전 해줘야 하는 작업이 있는데 바로 table에 저장되 있는 모든 weak reference들을 nil로 만들어줘야 한다는 것이다!

이것을 zeroing weak 라고도 부른다고 한다. 그러나 이런 접근 방식은 멀티 쓰레드에서 동시 접근이 일어날 때 많은 오버헤드가 있다고 한다. 인스턴스가 할당 해제되기 시작할 때부터 모든 상황에서 해당 인스턴스에 대한 접근을 해서는 절대 안된다.

 

swift에서는 오버헤드가 큰 위 방식 대신

Swift의 모든 object는 두 개의 레퍼런스 카운터를 유지한다.

  1. strong reference counter - ARC로 하여금 안전하게 메모리에서 해제할 수 있게 해주는 카운터
  2. additional weak reference counter - 얼마나 많은 unowned 혹은 weak 참조가 생겼는지를 나타내는 카운터로, 이 counter가 0에 도달했을 때, 비로소 object가 할당 해제된다.

모든 ojbect는 사실 unowned reference가 모두 release 될 때까지 해제되지 않는다. 여전히 object에 접근할 수는 있지만, uninitialized 상태가 될 뿐이다. (deinit 구문이 호출되지만, 여전히 힙에서 해제되진 않은 상태)

unowned 참조가 생성될 때마다, unowned reference counter가 atomic하게 증가되며, unowend reference를 사용할 때마다 항상 strong reference counter를 확인한다. 그러나 strong reference counter가 0일 때 접근하면 앱이 충돌나고 종료되게 된다.

 

클로저와 관련해서,

클로저는 기본적으로 reference 타입으로 저장될 때 heap 영역에 저장된다.

클로저가 heap영역에 저장될 때 사용하는 주변의 context를 같이 저장한는데 이것을 클로저의 캡처라고 한다.

클로저의 캡처는 참조 방식으로 주변의 context를 사용할 때 접근한다(클로저가 실행될 때 평가)(도중에 context내부의 값이 변경되더라도 변경된 값을 사용한다)

 

클로저에서 context를 참조 할 때 역시 reference count를 증가시키는데 이를 막으려면 클로저의 캡처리스트를 사용할 수 있다.

캡처리스트를 사용하게 되면 해당 context를 value 타입으로 복사해 클로저와 함께 저장하는데, 클로저가 생성될 때 저장한다(클로저가 생성될 때 평가)(도중에 context내부의 값이 변경되더라도 반영 안됨)

 

reference 타입을 캡처리스트에 담는 다면 해당 인스턴스의 reference(주소값) 자체를 값 타입으로 복사하게 되므로 reference count가 증가된다(어떤 방식이든 최종적으로 reference type의 주소값을 통해 접근하게 되므로 변경이 반영됨), weak이나 unowned 키워드를 사용해 refernece 타입의 인스턴스를 reference count를 증가시키지 않고 주소값을 복사해 캡처할 수 있다.

 

 

 

출처:

https://velog.io/@hojonge/weak-vs-unowned-Swift

https://velog.io/@kimdo2297/%ED%81%B4%EB%A1%9C%EC%A0%B8-%EC%BA%A1%EC%B3%90%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-about-closure-capture