[Swift] ARC (Automatic Reference Counting)
- 앱의 메모리 사용량을 추적하고 관리
- 어떠한 클래스 인스턴스가 더 이상 필요하지 않을 때 인스턴스에 할당된 메모리 자동해제
- Reference Type 인 클래스 인스턴스에만 적용. Value Type 에는 해당되지 않음
동작 방식
- 클래스가 새 인스턴스를 생성할때마다 ARC 는 메모리 할당
- 인스턴스의 메모리가 해제되면 더 이상 해당 인스턴스에 접근 불가능
- 그래서 ARC 는 인스턴스가 언제까지 필요한지 알기 위해 인스턴스를 참조하는 프로퍼티, 상수, 변수의 수를 추적함
클래스 인스턴스를 할당하기 위해 변수를 3개 선언하고, 나중에 nil
을 할당하여 해제해주기 위해 Optional
로 선언합니다.
reference1
변수에 클래스 인스턴스를 생성하는 순간 메모리가 할당되어 Reference Count 가 증가합니다. reference2
reference3
변수에 각각 이미 생성된 인스턴스를 할당하여 주는데도 Reference Count 가 증가합니다. 이는 변수에 인스턴스를 할당할 시 아무 키워드도 적어주지 않으면 디폴트로 강한 참조가 적용되기 때문입니다.
할당 해제의 경우, 2개의 변수에 nil 값을 넣어주어도 Reference Count 는 0 이 되지 않아 deinit
소멸자가 호출되지 않습니다. Class 는 Reference Type 이기 때문에 모두 같은 메모리 주소를 가리키고 있기 때문입니다.
마지막 남은 하나의 변수를 해제할 시 Reference Count 가 0이 되고 deinit
소멸자가 호출됩니다. 이 때 인스턴스가 메모리에서 해제됩니다.
순환 참조
- 인스턴스에서 다른 인스턴스에 대한 강한 참조를 가지고 있을 때 참조하는 인스턴스가 해제되었음에도 불구하고 참조를 계속 유지하는 문제
- 아무런 키워드도 입력하지 않고 강한 참조를 이용한다면 순환참조 문제가 일어날 수 있음
weak
혹은unowned
를 이용하여 해결
위의 코드에서 Person
과 Apartment
클래스는 각각 서로를 프로퍼티로 가지고 있습니다. 인스턴스를 생성하면 메모리가 할당되어 각자 RC 가 1씩 증가합니다.
서로를 프로퍼티로 가지고 있으니, 서로의 변수에 각자 인스턴스를 할당해보겠습니다.
현재 모든 변수는 강한 참조를 갖고 있기 때문에 Person
인스턴스의 apartment
변수에 새롭게 인스턴스가 할당되며 Apartment
의 RC 가 1 증가하고, Apartment
인스턴스의 tenant
변수에도 인스턴스가 할당되어 Person
의 RC 가 1 증가합니다.
인스턴스는 2개가 생성되었지만 모두 강한 참조를 가지고 있기 때문에 각각의 RC 는 모두 2가 됩니다. 이 상황에서 각자의 인스턴스를 해제하면 문제가 발생합니다.
이렇게 각각 john
과 unit4A
변수의 데이터는 할당 해제 되었지만, RC 는 1 씩 남게 됩니다.
이렇게 Person
과 Apartment
인스턴스 사이의 강한 참조는 해제되지 않아 더 이상 필요하지 않은 데이터가 메모리에서 해제되지 않고 남아있는 문제가 발생합니다.
Weak 과 Unowned
- weak 을 이용하면 약한참조가 되어 RC 가 증가하지 않음
- unowned 는 소유되지 않은 참조로 마찬가지로 RC 가 증가하지 않음
Weak
약한 참조는 ARC 가 참조된 인스턴스를 처리하는 것에 관여하지 않습니다. weak
키워드를 이용하여 약한 참조를 사용하면 해당 인스턴스는 해제될 수 있고, 해당 변수는 nil
로 설정됩니다.
그렇기 때문에 약한 참조를 이용할 시 항상 var
을 이용한 변수로 선언해야 하며 nil
로 설정될 수 있도록 Optional
로 선언되어야 합니다.
위의 코드와 같은 코드지만, Apartment
클래스의 tenant
변수가 weak
으로 선언된 것을 확인할 수 있습니다.
그렇기 때문에 약한 참조가 이루어져 tenant
변수에 인스턴스가 할당되더라도 Person
인스턴스의 RC 는 증가하지 않습니다.
그럼 여기서 john
변수에 nil
을 할당하여 Person
인스턴스를 해제해보겠습니다.
Person
인스턴스에 대해 강한 참조가 하나밖에 없었기 때문에 RC 는 0 이 되고 Person
인스턴스는 메모리에서 해제됩니다.
john
변수에서 Apartment
를 참조하던 apartment
변수도 사라지기 때문에 Apartment
에 대한 강한 참조도 하나 사라지며 Apartment
인스턴스의 RC 도 1로 감소합니다.
Unowned
약한 참조와 마찬가지로 unowned
또한 인스턴스 참조 시 RC 를 변경하지 않습니다. unowned
는 참조한 어떤 인스턴스의 수명이 동일하거나 더 길때 사용하는데, 메모리가 해제되지 않는다는 확신이 있을때 사용합니다.
메모리가 해제되지 않는다는 확신이 있기 때문에 nil
값을 가질 수 없고, 따라서 Optional
일 수 없습니다.
그래서 unowned
로 참조된 인스턴스가 해제된 후 접근하려하면 런타임 에러가 발생합니다.
위의 코드에서 CreditCard
클래스의 customer
변수는 인스턴스를 참조할 때 unowned
참조를 사용합니다.
이는 CreditCard
가 존재하는 한 Customer
은 반드시 존재할 것이라는 확신이 있다는 뜻입니다.
john
이라는 Customer
인스턴스를 생성하고 card
변수에 CreditCard
인스턴스를 생성해 할당해주겠습니다.
Customer
인스턴스는 CreditCard
인스턴스에 대해 강한 참조를 가지고 있지만, CreditCard
인스턴스는 Customer
인스턴스에 대해 unowned
참조를 가지고 있다는 것을 확인할 수 있습니다.
john
변수에 nil
을 넣어 Customer
인스턴스에 대한 강한 참조를 해제한다면 Customer
인스턴스의 RC 는 0이 되어 메모리에서 해제됩니다.
이렇게 되면 CreditCard
에 대한 강한 참조도 사라지며 모든 인스턴스의 메모리가 해제됩니다.
Unowned Optional References
클래스에 unowned
참조를 Optional
하게 표시할 수 있습니다. ARC ownership 모델에서는 unowned
참조와 weak
참조는 동일한 Context 에서 사용될 수 있습니다.
차이점은 unowned
참조를 Optional
하게 사용할때는 항상 유효한 객체를 참조하거나 nil
로 설정되어 있는지 확인해야 합니다.
Department
클래스는 학과가 요구하는 강의 코스에 대한 정보를 가지고 있기 때문에 courses
변수에 대해 강한 참조를 가지고 있습니다.
Course
클래스는 2개의 unowned
변수를 가지고 있는데, 강의에 대한 학과 정보는 필수적이기 때문에 department
변수는 Optional
로 선언되지 않았고, 몇몇 강의는 이후에 수강해야할 강의가 존재하지 않을수도 있기 때문에 nextCourse
변수는 Optional
로 선언되었습니다.
위의 코드는 학과정보를 담는 department
변수와 3개의 강의 정보를 담고 있는 변수를 보여주고 있습니다.
intro
와 intermediate
는 이후 들어야 할 강의가 존재하기 때문에 nextCourse
프로퍼티를 저장하고 있습니다.
Unowned Optional Reference 는 강한 참조를 가지고 있지 않기 때문에 ARC 가 인스턴스를 해제하는 것에 관여하지 않습니다.
unowned
참조가 nil
이 될수 있다는 점만 제외하면 ARC 에서 수행하는 것과 동일하게 작동합니다.
Non-Optional
한 unowned
참조처럼, nextCourse
변수는 항상 해제되지 않은 인스턴스를 참조하도록 해야 합니다.
이를 위해서는 만약 deparment.courses
에서 한 course
를 제거한다면 다른 코스들이 가지고 있을 reference 들도 제거해줘야 합니다.
Unowned References and Implicity Unwrapped Optional Properties
Weak 참조와 Unowned 참조는 강한 참조로 인해 발생하는 문제점을 해결하기 위해 사용되었습니다.
이번에는 2개의 프로퍼티에 항상 값이 있어야 하며 초기화가 완료되고 나서는 nil
이 되서는 안되는 경우입니다.
City
클래스의 인스턴스가 Country
클래스의 생성자 내부에서 생성된것을 확인할 수 있습니다. 하지만 Country
인스턴스가 완전히 생성되기 전에는 City
클래스의 생성자에 self
를 넘겨줄 수 없습니다.
이런 부분을 대처하기 위해서 Country
클래스의 capitalCity
프로퍼티를 Implicity Unwrapped Optional 로 선언하였습니다. capitalCity
프로퍼티는 기본값으로 nil
을 가지고 있지만, Unwrapping 과정 없이 접근이 가능해집니다.
capitalCity
가 기본값으로 nil
을 가지고 있기 때문에, Country
클래스의 생성자 내에서 name
프로퍼티가 설정되는 즉시 Coutnry
인스턴스는 완전히 생성된 것으로 간주됩니다. 그렇기 때문에 City
클래스에 self
를 전달할 수 있게 됩니다.
위처럼 Implicity Unwrapped Optional 을 사용한다는 것은 2단계 클래스 이니셜라이저 요구사항이 충족됨을 의미합니다. capitalCity
프로퍼티는 초기화 후엔 Optional
이 아닌 값처럼 사용하고 접근할 수 있으며 강한 참조로 인해 생기는 순환참조 문제도 해결할 수 있습니다.
Swift ARC vs Java GC
마무리
오늘은 Swift 언어에서 ARC 의 역할과 강한참조로 인해 발생하는 순환참조, 이를 해결할 수 있는 약한참조와 Unowned 참조에 대해 알아보았습니다.
내용이 많은 관계로 클래스 내 프로퍼티에 클로저를 할당하는 경우에 대해서는 다음에 알아보도록 하겠습니다.
참고 링크
'iOS 개발 > Swift' 카테고리의 다른 글
[Swift] 타입 캐스팅 (Type Casting) (0) | 2022.05.19 |
---|---|
[Swift] 프로토콜 (Protocols) (0) | 2022.05.13 |
[Swift] 확장 (Extensions) (0) | 2022.05.13 |
[Swift] ARC (2/2) (0) | 2022.04.29 |
[Swift] 클로저 (Closures) (0) | 2022.04.22 |