-
Swift ARC(Automatic Reference Counting)iOS/Wiki 2022. 1. 15. 21:13
안녕하세요!! 이번엔 Swift의 Automatic Reference Counting에 대해서 정리했습니다.
이번 글도 노션에서 옮겨와 말이 짧습니다!! 양해해주세용😽
ARC란?
Swift의 메모리 관리 시스템으로, Automatic Reference Counting이라고 한다.
더이상 필요하지 않은(참조하는 변수, 상수가 없을 경우) 인스턴스를 자동으로 메모리에서 해제해준다.
class Apartment { var unit: String var tenant: Person? init(unit: String) { self.unit = unit } deinit { print("\\(unit) is destoryed...") } } class Person { var name: String var regidence: Apartment? init(name: String) { self.name = name } deinit { print("\\(name) is gone...") } }
다음과 같이 Apartment와 Person 두 클래스를 만들었다.
var yeonpark: Person? = Person(name: "yeonpark") var apt: Apartment? = Apartment(unit: "102동")
그리고 yeonpark, apt라는 인스턴스를 만들었다.
상수 또는 변수에 클래스 인스턴스를 할당할 때 해당 인스턴스에 대한 Reference Counting이 증가한다.
이 시점에서 Reference Counting이 1 증가한다.
해당 인스턴스가 유지되고 강력한 참조가 존재하는 한 메모리에서 할당 해제가 되지 않는다.
이를 강한 참조라고 한다.
yeonpark!.regidence = apt apt!.tenant = yeonpark
이제 yeonpark의 regidence, apt의 tenant에 서로를 넣어주게 되면,
강한 참조가 하나씩 더 늘어나므로 Reference Counting은 2가 된다.
yeonpark = nil apt = nil
이렇게 인스턴스에 nil을 할당하면, Reference Counting이 0이 되어 deinit이 호출되어야 하지만, 호출되지 않는다.
바로 위에서 서로가 강하게 참조하고 있어 Reference Counting이 남아있기 때문이다.
ARC는 Reference counting이 0이 되면 인스턴스를 자동으로 메모리에서 해제해준다.
하지만, 두 인스턴스가 서로 강한 참조로 연결되어 있다면, 인스턴스에 nil을 할당해도 서로에 대한 참조가 남아있어 할당해제가 되지 않는다. 이를 강한 순환 참조라고 한다.
강한 순환 참조 상태에서는 해당 인스턴스들이 메모리에 영원히 남아있게 되어 메모리 누수가 발생한다.
그럼 강한 순환 참조는 어떻게 해결할 수 있을까?
weak와 unowned를 이용하면 강한 순환 참조를 해결할 수 있다.
Weak
weak는 말 그대로 약한 참조다.
Reference Counting을 증가시키지 않으며,
참조하는 인스턴스가 메모리에서 해제되면 자동으로 weak 참조를 nil로 설정해서 메모리에서 해제된다.
항상 옵셔널로 설정되어야 하며(런타임 중에 nil이 될 수 있어야 하니까)
변수(var)로 선언되어야 한다.(런타임 중에 nil값으로 바뀔 수 있어야 하니까)
다른 인스턴스의 수명이 더 짧은 경우(먼저 할당 해제할 수 있는 경우)에 사용한다.
class Apartment { var unit: String weak var tenant: Person? // Person 인스턴스의 수명이 더 짧으니 weak로 선언 init(unit: String) { self.unit = unit } deinit { print("\\(unit) is destoryed...") } } class Person { var name: String var regidence: Apartment? init(name: String) { self.name = name } deinit { print("\\(name) is gone...") } } var yeonpark: Person? = Person(name: "yeonpark") var apt: Apartment? = Apartment(unit: "102동") yeonpark!.regidence = apt apt!.tenant = yeonpark yeonpark = nil
실행결과
yeonpark is gone...
yeonpark 인스턴스에 nil을 넣는 순간, yeonpark이 가리키고 있는 Person 인스턴스에 대한 Reference Counting이 줄어든다.
apt에서의 Person은 약한 참조로 연결되어 있어, Reference Counting를 높이지 않았기 때문에
Reference Counting이 0이 되어 메모리에서 사라진다. (yeonpark is gone...😢)
그리고 apt에 있는 Person은 위에서 말했듯이,
참조하는 인스턴스가 메모리에서 해제되면 자동으로 weak 참조를 nil로 설정해서 메모리에서 해제된다.
위 설정에 의해 nil값으로 바뀌게 된다. 즉, 런타임에서 Apartment의 tanent가 nil이 된다.
unowned
미소유 참조라고 한다.
얘도 Reference Counting을 증가시키지 않는다.
참조하는 인스턴스가 항상 값이 있을 것이라고 가정하여 메모리에서 해제되어도 nil로 만들어 주지 않는다.(포인터가 해제된 메모리 영역을 가리키고 있다.)
해제된 메모리에 접근할 시, Crash가 발생한다. 그래서 다른 인스턴스의 수명이 동일하거나 더 긴 경우에 사용한다.
런타임에 nil이 될 일이 없으므로, 옵셔널로 선언되지 않는다. (Swift 5.0 이상부터 옵셔널도 지원해준다.....)
런타임에 nil로 값이 바뀔 일이 없으므로, let으로도 선언할 수 있다.
class Apartment { var unit: String unowned var tenant: Person? // weak에서 unowned로 바꿔서 선언 init(unit: String) { self.unit = unit } deinit { print("\\(unit) is destoryed...") } } class Person { var name: String var regidence: Apartment? init(name: String) { self.name = name } deinit { print("\\(name) is gone...") } } var yeonpark: Person? = Person(name: "yeonpark") var apt: Apartment? = Apartment(unit: "102동") yeonpark!.regidence = apt apt!.tenant = yeonpark yeonbae = nil print(apt?.tenant)
실행결과
yeonpark is gone... Fatal error: Attempted to read an unowned reference but object 0x600003d79950 was already deallocated
위 코드를 실행하면, apt의 tenant에 접근하려고 할 시 에러가 발생한다.
위에서 말한 설정 중 하나인
해제된 메모리에 접근할 시, Crash가 발생한다.
때문이다.
yeonpark에 nil을 할당해서 Reference Count를 0으로 만들고 할당 해제를 시켜놨지만,
unowned 참조는 항상 값이 있을거라고 판단하기 때문에 해제된 메모리 주소를 가리키고 있는 것이다.
이렇게 이미 해제된 메모리에 접근하는 포인터를 고아 포인터(dangling Pointer)라고 한다.
그래서 항상 unowned는 다른 인스턴스의 수명이 같거나, 더 긴 경우에만 사용해야한다!
'iOS > Wiki' 카테고리의 다른 글
POP(Protocol-Oriented Programming) in Swift (1/2) (0) 2022.03.28 ReactorKit 사용기 (0) 2022.03.05 Localization (0) 2022.01.14 페이지네이션(Pagination) (0) 2022.01.13 UserDefaults (0) 2022.01.11