- Swift는 클래스 타입의 프로퍼티와 작업할 때 강한 참조 사이클을 해결하기 위해 2가지 방법을 제공한다.
- weak references
- unowned references
- 약한 참조와 미소유 참조를 사용하면 참조 사이클의 한 인스턴스가 강한 유지 없이 다른 인스턴스를 참조할 수 있다.
- 다른 인스턴스의 수명이 더 짧은 경우 즉, 다른 인스턴스가 먼저 할당 해제될 수 있을 때 약한 참조를 사용한다. 1의 예제에서 아파트는 어느 시점에 소유자가 없을 수 있는 것이 적절하므로 이러한 경우 약한 참조는 참조 사이클을 끊는 적절한 방법이다. 반대로 다른 인스턴스의 수명이 동일하거나 더 긴 경우 미소유 참조를 사용한다.
- 약한 참조 (weak reference) 는 참조하는 인스턴스를 강하게 유지하지 않는 참조이므로 ARC가 참조된 인스턴스를 처리하는 것을 중지하지 않습니다. 이러한 동작은 참조가 강한 참조 사이클의 일부가 되는 것을 방지합니다. 프로퍼티 또는 변수 선언 전에
weak
키워드를 위치시켜 약한 참조를 나타냅니다. - 약한 참조는 참조하는 인스턴스를 강하게 유지하지 않기 때문에 약한 참조가 참조하는 동안 해당 인스턴스가 할당 해제될 수 있습니다. 따라서 ARC는 참조하는 인스턴스가 할당 해제되면
nil
로 약한 참조를 자동으로 설정합니다. 그리고 약한 참조는 런타임에 값을nil
로 변경하는 것을 허락해야 하므로 항상 옵셔널 타입의 상수가 아닌 변수로 선언됩니다.
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
![[스크린샷 2023-01-30 14.04.18.png|500]]
Person
인스턴스는Apartment
인스턴스에 대해 아직 강한 참조를 가지고 있지만Apartment
인스턴스는 이제Person
인스턴스에 대해 약한 참조를 가지고 있습니다. 이것은john
변수에nil
을 설정하여 강한 참조를 끊으면Person
인스턴스에 대해 더이상 강한 참조가 아님을 의미한다.
john = nil
// Prints "John Appleseed is being deinitialized"
![[스크린샷 2023-01-30 14.09.59.png|500]]
Apartment
인스턴스에 대한 유일한 강한 참조는unit4A
변수에서 가져온 것입니다. 강한 참조를 끊으면Apartment
인스턴스에 대한 강한 참조는 더이상 없다.
unit4A = nil
// Prints "Apartment 4A is being deinitialized"
![[스크린샷 2023-01-30 14.10.52.png|500]]
- In systems that use garbage collection, weak pointers are sometimes used to implement a simple caching mechanism because objects with no strong references are deallocated only when memory pressure triggers garbage collection. However, with ARC, values are deallocated as soon as their last strong reference is removed, making weak references unsuitable for such a purpose.
- 약한 참조와 마찬가지로 미소유 참조 (unowned reference) 는 참조하는 인스턴스를 강하게 유지하지 않습니다. 그러나 약한 참조와 다르게 미소유 참조는 다른 인스턴스의 수명이 같거나 더 긴 경우에 사용됩니다. 프로퍼티 또는 변수 선언 전에
unowned
키워드를 위치시켜 미소유 참조를 나타냅니다. - 약한 참조와 달리 미소유 참조는 항상 값을 갖도록 예상됩니다. 결과적으로 미소유로 만들어진 값은 옵셔널로 만들어 지지 않고 ARC는 미소유 참조의 값을
nil
로 설정하지 않습니다. - 참조가 항상 할당 해제되지 않은 인스턴스를 참조한다고 확신하는 경우에만 미소유 참조를 사용해야 한다. 인스턴스가 할당 해제된 후에 미소우 참조의 값에 접근하려고 하면 런타임 에러가 발생한다.
Customer
와CreditCard
간의 관계는 위의 약한 참조 예제에서 본Apartment
와Person
간의 관계와 약간 다르다. 이 데이터 모델은 고객은 신용카드를 가지고 있거나 가지고 있지 않을 수 있지만 신용카드는 항상 고객과 연관되어 있다.CreditCard
인스턴스는 참조하는Customer
보다 오래 지속되지 않습니다. 이것을 표현하기 위해Customer
클래스는 옵셔널card
프로퍼티를 가지지만CreditCard
클래스는 미소유와 옵셔널이 아닌customer
프로퍼티를 가집니다.- 또한 새로운
CreditCard
인스턴스는 사용자 정의CreditCard
초기화 구문에number
값과customer
인스턴스를 전달해서만 생성될 수 있습니다. 이렇게 하면CreditCard
인스턴스가 생성될 때CreditCard
인스턴스에 항상 연관된customer
인스턴스를 가지고 있습니다. - 신용카드는 항상 고객을 가지고 있으므로 강한 참조 사이클을 피하기 위해
customer
프로퍼티에 미소유 참조로 정의합니다
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
![[스크린샷 2023-01-30 14.23.36.png|500]]
Customer
인스턴스는 이제CreditCard
인스턴스에 대한 강한 참조를 가지고 있고CreditCard
인스턴스는Customer
인스턴스에 대해 미소유 참조를 가지고 있습니다.john
변수에 의해 강한 참조를 끊을 때 미소유customer
참조 때문에Customer
인스턴스에 대해 더이상 강한 참조를 가지지 않습니다 ![[스크린샷 2023-01-30 14.29.49.png|500]]- 더이상
Customer
인스턴스에 대해 강한 참조가 아니므로 할당 해제됩니다. 후에CreditCard
인스턴스에 대해 더이상 강한 참조가 아니므로 이것도 할당 해제됩니다:
john = nil
// Prints "John Appleseed is being deinitialized"
// Prints "Card #1234567890123456 is being deinitialized"
- 위의 마지막 코드는
john
변수를nil
로 설정한 후에Customer
인스턴스와CreditCard
인스턴스 모두 "deinitialized" 메서지를 출력하는 초기화 해제 구문을 보여줍니다. ![[Pasted image 20230130150508.png]] - unowned, weak reference에 대한 나의 이해
- 클래스에 옵셔널 참조를 미소유로 표기할 수 있습니다. ARC 소유권 모델 측면에서 미소유 옵셔널 참조 (unowned optional reference)와 약한 참조는 모두 같은 컨텍스트에서 사용될 수 있습니다. 차이점은 미소유 옵셔널 참조를 사용할 때 유효한 객체를 참조하거나
nil
로 설정되어 있는지 확인해야 합니다.
class Department {
var name: String
var courses: [Course]
init(name: String) {
self.name = name
self.courses = []
}
}
class Course {
var name: String
unowned var department: Department
unowned var nextCourse: Course?
init(name: String, in department: Department) {
self.name = name
self.department = department
self.nextCourse = nil
}
}
Department
는 과에서 제공하는 각 과정에 강한 참조를 유지합니다. ARC 소유권 모델에서 과는 과정을 소유하고 있습니다.Course
는 과에 대한 것과 객체를 소유하지 않은 학생이 수강해야 하는 다음 과정에 대한 2개의 미소유 참조를 가지고 있습니다. 모든 과정은 과의 부분이므로department
프로퍼티는 옵셔널이 아닙니다. 그러나 일부 과정은 후속 과정이 없기 때문에nextCourse
프로퍼티는 옵셔널 입니다.
let department = Department(name: "Horticulture")
let intro = Course(name: "Survey of Plants", in: department)
let intermediate = Course(name: "Growing Common Herbs", in: department)
let advanced = Course(name: "Caring for Tropical Plants", in: department)
intro.nextCourse = intermediate
intermediate.nextCourse = advanced
department.courses = [intro, intermediate, advanced]
-
위의 코드는 과와 그 과의 3개의 과정을 생성합니다. 소개와 중급 과정 모두
nextCourse
프로퍼티에 저장된 다음 과정을 제안하며 이는 학생이 과정을 완료한 후 수강해야 하는 과정에 대한 미소유 옵셔널 참조를 유지합니다. -
미소유 옵셔널 참조는 래핑하는 클래스의 인스턴스에 강하게 유지하지 않으므로 ARC가 인스턴스를 할당 해제하는 것을 방지하지 않습니다. 미소유 옵셔널 참조가
nil
이 될 수 있다는 점을 제외하고 미소유 참조는 ARC에서 수행하는 것과 동일하게 동작합니다.
- 위에서 약한 참조와 미소유 참조에 대한 예제는 강한 참조 사이클을 중단해야 하는 일반적인 2가지 시나리오를 다룹니다.
Person
과Apartment
예제는 둘 다nil
이 될 수 있는 프로퍼티가 강한 참조 사이클을 유발할 수 있는 가능성이 있는 상황을 보여줍니다. 이 시나리오는 약한 참조로 해결하는 것이 가장 좋습니다.Customer
와CreditCard
예제는nil
이 허용되는 하나의 프로퍼티와nil
일 수 없는 프로퍼티가 강한 참조 사이클을 유발할 수 있는 가능성이 있는 상황을 보여줍니다. 이 시나리오는 미소유 참조로 해결하는 것이 가장 좋습니다.- 그러나 두 프로퍼티 모두 항상 값이 있고 초기화가 완료되면
nil
이 되어서는 안되는 세번째 시나리오가 있습니다. 이 시나리오에서는 한 클래스의 미소유 프로퍼티를 다른 클래스에 암시적으로 언래핑된 옵셔널 프로퍼티와 결합하는 것이 유용합니다.
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// Prints "Canada's capital city is called Ottawa"
- 두 클래스 간의 상호 종속성을 설정하기 위해
City
에 대한 초기화 구문은Country
인스턴스를 가지고 있고country
프로퍼티에 저장합니다. City
에 대한 초기화 구문은Country
에 대한 초기화 구문 내에서 호출됩니다. 그러나Country
에 대한 초기화 구문은 새로운Country
인스턴스가 완벽히 초기화 될 때까지City
초기화 구문에self
를 전달할 수 없습니다.- 이 요구사항을 처리하려면
Country
의capitalCity
프로퍼티를 타입 설명의 끝에 느낌표 (City!
)로 표시되는 암시적 언래핑된 옵셔널 프로퍼티로 선언합니다.capitalCity
프로퍼티는 다른 옵셔널과 같이nil
의 기본값을 가지지만 언래핑 할 필요없이 값에 접근할 수 있다는 의미입니다. capitalCity
는 기본nil
값을 가지므로 새로운Country
인스턴스는Country
인스턴스가 초기화 구문 내에서name
프로퍼티를 설정하는 즉시 새로운Country
인스턴스는 완벽히 초기화 된 것으로 간주합니다. 이것은Country
초기화 구문은name
프로퍼티가 설정되는 즉시 암시적self
프로퍼티를 참조하고 전달할 수 있다는 의미입니다. 따라서Country
초기화 구문은capitalCity
프로퍼티를 설정할 때City
초기화 구문에 대한 하나의 파라미터로self
를 전달할 수 있습니다.- 이 모든 것은 강한 참조 사이클을 만들지 않고 단일 구문으로
Country
와City
인스턴스를 생성하고capitalCity
프로퍼티는 옵셔널 값을 언래핑 하기위해 느낌표를 사용할 필요없이 직접 접근할 수 있다는 의미입니다: - 위의 예제에서 암시적 언래핑된 옵셔널의 사용은 모든 2단계 클래스 초기화 구문 요구사항을 충족한다는 의미입니다.
capitalCity
프로퍼티는 초기화가 완료되면 옵셔널이 아닌 값처럼 사용되고 접근할 수 있지만 강한 참조 사이클은 피할 수 있습니다. ![[Pasted image 20230130165036.png]]
- https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html
- https://bbiguduk.gitbook.io/swift/language-guide-1/automatic-reference-counting
- https://developer.apple.com/library/archive/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
- [[6. ARC (1)]]
- [[8. ARC (3)]]
- [[4. weak var]]
- #Swift/ARC
- #Swift/ARC/strong
- #Swift/ARC/weak
- #Swift/ARC/unowned