이번에는 ARC 공식문서에서
Unowned References and Implicitly Unwrapped Optional Properties
를 다뤄보려고 합니다!
Unowned References 미소유 참조 뿌시기..
스따뜨..!!!
약간의 의역이 포함될 예정이니 오역이나 업데이트가 필요한 부분은 꼭 댓글로 알려 주세요..! :)
Unowned References (미소유 참조)
약한 참조처럼, 미소유 참조는 참조하는 인스턴스를 강하게 붙들지 않습니다. 하지만 약한 참조와는 다르게, 미소유 참조는 다른 인스턴스가 같거나 더 긴 수명을 가질 때 사용됩니다. 당신은 프로퍼티나 변수 선언 앞에 unowned 키워드를 붙여서 미소유 참조를 명시할 수 있습니다.
약한 참조와 다르게, 미소유 합조는 항상 값을 갖도록 요구됩니다. 결과적으로 값을 미소유로 표시한다고 해서 옵셔널이 되는 것이 아니고, ARC는 미소유 참조의 값을 절대 nil로 설정하지 않습니다.
중요
참조가 항상 메모리에서 해제되지 않은 인스턴스를 참조하는 것이 분명할 때만 미소유 참조를 사용하세요.
미소유 참조하는 인스턴스가 메모리에서 해제된 후에 그 인스턴스의 값에 접근하면, 런타임 에러를 맛볼 것입니다.
아래의 예시에는 은행 고객과 그 고객이 가질 가능성이 있는 신용 카드의 모델이 되는(형태/표본/견본이 되는) Customer와 CreditCard라는 두 클래스를 정의되어 있습니다. 이 두 클래스는 서로의 인스턴스를 프로퍼티로 갖습니다. 이 관계는 순환참조가 일어날 가능성이 있죠.
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") }
}
주의
CreditCard 클래스의 number 프로퍼티는 Int가 아니라 UInt64로 정의되어 있습니다. 이것은 32 비트와 64 비트 시스템에서 number 프로퍼티가 16 자리의 카드 번호를 저장하기에 충분하도록 하기 위함입니다.
다음 코드 스니펫은 특정 고객에 대한 참조를 저장하는 데 사용될 john이라는 옵셔널 Customer 변수를 정의합니다. 이 변수는 옵셔널이기 때문에 초기값으로 nil을 갖습니다:
var john: Customer?
이제 Customer 인스턴스를 생성하고, 그 고객의 card 프로퍼티에 할당할 새로운 CreditCard 인스턴스의 초기화에 그 Customer 인스턴스를 사용할 수 있습니다.
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
두 인스턴스를 연결한 후 참조의 모습은 다음과 같습니다:
이제 Customer 인스턴스는 CreditCard 인스턴스를 강한 참조하고, CreditCard 인스턴스는 Customer 인스턴스를 미소유 참조합니다.
미소유 customer 참조때문에, john 변수가 붙들고 있는 강한 참조를 깨면, 더이상 Customer 인스턴스를 강한 참조하는 것은 없게 됩니다:
더이상 Customer 인스턴스를 강한 참조하는 것이 없기 때문에, 인스턴스는 메모리에서 해제됩니다. 그 이후에는, 더이상 CreditCard 인스턴스를 강한 참조하는 것이 없기 때문에, CreditCard 인스턴스도 메모리에서 해제됩니다.
john = nil
// "John Appleseed is being deinitialized" 출력
// "Card #1234567890123456 is being deinitialized" 출력
위에 보이는 마지막 코드 스니펫은 john 변수가 nil로 설정된 이후 Customer 인스턴스와 CreditCard 인스턴스의 소멸자(디이니셜라이저)가 모두 "deinitialized" 메시지를 출력하는 것을 보여 줍니다.
주의
위의 예시들은 안전한 미소유 참조를 어떻게 사용하는지를 보여 줍니다. Swift는 또한 당신이 런타임 세이프티 체크를 비활성화시켜야 할 때를 위해 안전하지 않은 미소유 참조도 제공합니다. 안전하지 않은 모든 실행에 대해서, 그 코드의 안전성 확인에 대한 책임은 당신이 져야 합니다.
안전하지 않은 미소유 참조는 unowned(safe) 키워드를 작성함으로써 명시할 수 있습니다. 안전하지 않은 미소유 참조 인스턴스가 메모리에서 해제된 후 접근하려 하면, 당신의 프로그램은 그 인스턴스가 있던 메모리 위치에 접근하려 할 것이고, 이것이 안전하지 않은 실행입니다.
(흠. ....)
Unowned Optional References (미소유 옵셔널 참조)
당신은 클래스에 대한 옵셔널 참조를 미소유로 표기할 수 있습니다. ARC 소유 모델에 관해서는, 미소유 옵셔널 참조와 약한 참조는 둘 다 같은 상황에서 쓰일 수 있습니다. 차이점은 미소유 옵셔널 참조를 사용할 경우, 그것이 늘 유효한 객체를 참조하거나 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는 두 개의 미소유 참조를 갖는데, 하나는 학과에 대한 것이고 하나는 학생이 다음에 수강해야 할 과목입니다; 그 과목은 이 객제들 중 어느 것도 소유하지 않습니다. 모든 과목은 어떤 학과의 한 부분이므로, (Course의) department 프로퍼티는 옵셔널이 아닙니다. 하지만, 어떤 과목들은 이어지는 과목이 없으므로(다음에 수강해야 할 과목이 없다는 말.. 머 프로그래밍 기초1, 프로그래밍 기초2 이렇게 있다면 프기2를 말하는 것..) 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 개를 생성합니다. intro와 intermediate 과목은 둘 다 다음에 수강할 과목이 nextCourse 프로퍼티에 저장되어 있습니다. nextCourse 프로퍼티는 학생이 이 과목(intro와 intermediate)을 수강한 후에 들어야 할 과목들에 대한 미소유 참조를 유지합니다.
미소유 옵셔널 참조는 그것이 감싸는 클래스 인스턴스를 강하게 붙들고 있지 않기 때문에 ARC가 그 인스턴스를 메모리에서 해제하는 것을 막지 않습니다. 미소유 옵셔널 참조는 nil이 될 수 있다는 것을 제외하고, ARC에서 미소유 참조가 작동하는 것과 같은 방식으로 작동합니다.
논옵셔널 미소유 참조처럼, nextCourse가 항상 메모리에서 해제되지 않은 과목을 참조하도록 해야 합니다. 예를 들어 이 경우에는, department.courses에서 과목을 삭제하면, 다른 과목이 이 과목에 대해 가질 수 있는 모든 참조를 제거해야 합니다.
조금 덧붙이자면..
Department는 courses 프로퍼티에서 각 Course에 강한 참조를 하고 있으니까,
여기(department.courses)서 강한 참조를 끊어 버리면(과목을 삭제하면),
해당 과목의 RC는 내려가게 되고, 만약 0이 되어 버리면 ARC가 메모리에서 해제를 해버리는데,
이렇게 더이상 메모리에 없는 과목을, 다른 과목의 nextCourse가 미소유 참조하려 하고 있지는 않나 확인하라는 것!
미소유 참조는 약한 참조와는 다르게 자동으로 nil이 할당이 되지 않기 때문에,,,
혹쉬나 미소유 참조가 아직 되고 있다면 직접 nil을 할당해서 런타임 에러를 방지하자...! 라는 말 같다..!!
주의
옵셔널 밸류의 타입은 Swift standard library에서 열거형의 Optional 옵셔널 타입입니다. 하지만 옵셔널은 값 타입은 unowned로 표기될 수 없다는 규칙의 예외입니다.
클래스를 감싸는 옵셔널은 참조 카운팅을 사용하지 않기 때문에, 그 옵셔널에 강한 참조를 유지할 필요가 없습니다.
음 이 부분은 잘 이해가.. . ㅠ 아시는 분은 댓글 부탁드려요....
미소유 참조와 묵시적 옵셔널 해제 프로퍼티
상기한 약한 참조와 미소유 참조는 순환참조를 끊는 것이 필요한 흔한 경우를 다룹니다.
Person과 Apartment 예시는 nil이 될 수 있는 두 프로퍼티가 순환참조를 발생시킬 수 있는 상황을 보여 줍니다. 이 경우는 약한 참조로 해결하는 것이 최상입니다.
Customer와 CreditCard 예시는 nil이 될 수 있는 한 프로퍼티와 nil이 될 수 없는 다른 프로퍼티가 순환참조를 발생시킬 수 있는 상황을 보여 줍니다. 이 경우는 미소유 참조로 해결하는 것이 최상입니다.
하지만, 두 프로퍼티 모두 항상 값을 가져야 하면서 초기화 이후 nil을 절대 가질 수 없는 상황도 있습니다. 이 경우에는, 한 클래스는 미소유 프로퍼티를 갖게 하고, 다른 클래스는 묵시적 옵셔널 해제 프로퍼티를 갖게 하도록 하는 것이 유용합니다.
이것은 순환참조를 여전히 피하면서, 초기화가 끝나면 프로퍼티에 (옵셔널 언래핑 없이) 직접적으로 접근하는 것을 가능하게 해 줍니다. 이 섹션에서는 그러한 관계를 어떻게 성립할 수 있는지를 보여 줍니다.
아래 예시는 두 클래스 Country와 City를 정의하는데, 각 클래스는 서로의 인스턴스를 프로퍼티로 저장합니다. 이 데이터 모델에서, 각 나라는 반드시 수도가 있어야 하고, 각 도시는 반드시 한 나라에 속해야 합니다. 따라서 Country 클래스는 capitalCity 프로퍼티를 갖고, City 클래스도 country 프로퍼티를 갖습니다.
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
}
}
두 클래스 사이의 상호 의존을 구성하기 위해서, City의 이니셜라이저는 Country 인스턴스를 (매개변수로) 받고, 그 인스턴스를 country 프로퍼티에 저장합니다.
City의 이니셜라이저는 Country의 이니셜라이저 내부에서 호출됩니다. 하지만 2단계 초기화에서 설명한 대로 Country의 이니셜라이저는 새로운 Country 인스턴스가 완전히 초기화 될 때까지 self를 City 이니셜라이저로 전달할 수가 없습니다.
이런 요구사항을 해결하기 위해 Country의 capitalCity 프로퍼티를 묵시적 옵셔널 해제 프로퍼티로 선언합니다. 타입 어노테이션 끝에 느낌표로 명시해서요(City!). 이것은 capitalCity 프로퍼티가 다른 옵셔널처럼 nil을 초기값으로 가지면서도, (묵시적 해제 옵셔널에서 설명된 것처럼) 값을 언래핑하지 않고도 접근이 가능하다는 것을 의미합니다.
capitalCity가 초기값으로 nil을 갖기 때문에, 새로운 Country 인스턴스는 이니셜라이저에서 name 프로퍼티를 지정하자마자 완전히 초기화가 된 것으로 간주됩니다. Country 이니셜라이저가 name 프로퍼티를 지정하자마자 묵시적 self 프로퍼티를 참조하고 전달할 수 있다는 말이죠. 따라서 Country 이니셜라이저는 자신의 capitalCity 프로퍼티를 지정할 때, City 이니셜라이저에 self를 파라미터로 넘길 수 있습니다.
이 모든 것은 순환참조 없이 Country와 City 인스턴스를 하나의 표현만으로 생성할 수 있고, capitalCity 프로퍼티도 느낌표로 옵셔널 밸류를 언래핑할 필요 없이 바로 접근이 가능하다는 것을 의미합니다:
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// "Canada's capital city is called Ottawa" 출력
위 예시에서, 묵시적 해제 옵셔널의 사용은 2단계 클래스 이니셜라이저의 요구사항이 모두 충족됨을 의미합니다. 초기화가 끝나면, 순환참조를 피하면서 capitalCity 프로퍼티를 논옵셔널 밸류처럼 사용하거나 접근할 수 있습니다.
ㅎㅅㅎ 드디어 unowned 끝..!!
다음에는 클로저(ㄷㄷ)에서의 순환참조와 해결법 등..에 대해 다뤄 보고 공식문서의 ARC 파트를 마쳐 보려고 해요!
직역하다 보니 한국어로 읽었을 때 좀 어색한 부분들이 없잖아 있는 것 같은데 ㅎㅎ;;
그래도 도움이 될 수 있었음 좋겠습니당!!
오타, 오역 및 업데이트가 필요한 부분은 꼭 알려 주세여~~
'Swift > 공식문서' 카테고리의 다른 글
[Swift 공식문서 번역] ARC (Automatic Reference Counting) - 5. (마지막) (1) | 2024.10.30 |
---|---|
[Swift 공식문서 번역] ARC (Automatic Reference Counting) - 4. (0) | 2023.03.09 |
[Swift 공식문서 번역] ARC (Automatic Reference Counting) - 2. (0) | 2023.02.16 |
[Swift 공식문서 번역] ARC (Automatic Reference Counting) - 1. (0) | 2023.02.06 |