You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Swift는 자동 참조 카운팅(Automatic Reference Counting; ARC) 을 사용하여 앱의 메모리 사용을 추적하고 관리한다
대부분의 경우, 이는 Swift에서 메모리 관리는 “그냥 작동하는 (just works)” 것이며, 메모리 관리를 직접 생각할 필요가 없다
ARC는 클래스 인스턴스가 더 이상 필요하지 않을 때 해당 인스턴스가 사용한 메모리를 자동으로 해제한다
하지만, 몇몇 경우에 ARC 는 메모리 관리를 위해 코드 간의 관계에 대한 더 많은 정보를 요구한다
이번 챕터는 그 상황들을 설명하며 ARC 가 앱의 모든 메모리를 관리하게 해주는 방법을 보여준다
Swift에서 ARC 를 사용하는 건 Transitioning to ARC Release Notes에서 설명한 오브젝티브-C 에서 ARC 를 사용하는 접근법과 아주 비슷하다
‘참조 카운팅 (reference counting)’ 은 Swift의 메모리 관리 방법으로,
여기서 ‘메모리 관리’ 란 ‘동적 메모리를 자동으로 할당하고 해제하는 것’ 을 의미한다
프로그래밍에서 ‘동적 메모리’ 의 할당, 해제가 일어나는 곳을 ‘자유 저장 공간 (free store; 또는 heap)’ 이라고 하며,
‘참조’ 라는 말은 ‘자유 저장 공간’ 에 할당된 메모리 영역을 ‘참조한다 (refer to)’ 것에서 유래한 말이다
구조체나 열거체 같은 ‘값 타입 (value type)’ 은 ‘자유 저장 공간’ 이 아닌 ‘정적 메모리 공간’ 인 ‘스택 (stack)’ 에 생기므로 메모리 관리 대상이 아니다
이에 대한 더 자세한 정보는, 위키피디아의 ‘Memory management’ 항목에 있는 Dynamic memory allocation 부분과 Stack-based memory allocation 항목에서 얻을 수 있다
ARC의 작동 방식
새로운 클래스 인스턴스를 생성할 때마다, ARC 가 메모리 덩어리를 할당하여 그 인스턴스에 대한 정보를 저장한다
해당 메모리는 인스턴스의 타입에 대한 정보와, 그 인스턴스와 관계된 어떤 저장 프로퍼티의 값을 함께 가지고 있다
추가적으로, 더 이상 인스턴스가 필요없을 땐, ARC가 그 인스턴스에서 사용한 메모리를 해제하여
메모리를 대신 다른 용도로 사용할 수 있게 한다
이는 더 이상 클래스 인스턴스가 필요하지 않을 때, 이들이 메모리 공간을 차지하게 않도록 보장한다
하지만, ARC가 해제한 인스턴스가 여전히 사용 중인 거라면,
더 이상 그 인스턴스의 프로퍼티에 접근하거나,
그 인스턴스의 메소드를 호출하는 게 불가능해진다
진짜로, 인스턴스에 접근하려 시도하면, 앱이 크래쉬를 일으켜 종료된다
인스턴스가 아직 필요한 동안에는 사라지지 않도록 하기 위해,
ARC는 현재 얼마나 많은 프로퍼티, 상수, 및 변수가 각각의 클래스 인스턴스를 참조하고 있는지 추적한다
ARC는 해당 인스턴스에 적어도 하나의 활동 중인 참조가 존재하는 한 인스턴스를 해제하지 않게 된다
이를 가능하게 하려고, 프로퍼니나, 상수, 또는 변수에 클래스 인스턴스를 할당할 때마다
해당 프로퍼티나, 상수, 또는 변수는 인스턴스에 대한 강한 참조 (strong reference)를 만든다
참조를 “강한 (strong)” 참조라고 하는 건 그 인스턴스를 꽉 쥐고 있기 때문이며, 강한 참조가 남아 있는 한 메모리 해제를 허용하지 않는다
ARC 의 실제 사례 (ARC in Action)
아래는 자동 참조 카운팅의 작동 방법에 대한 예제이다
예제는 Person 이라는 단순한 클래스로 시작하는데, 이는 name 이라는 상수 저장 프로퍼티를 정의한다
classPerson{letname:Stringinit(name:String){self.name = name
print("\(name) is being initialized")}deinit{print("\(name) is being deinitialized")}}
Person 클래스에 있는 이니셜라이저는 인스턴스의 name 프로퍼티를 설정하고 초기화가 진행중이라는 메시지를 print 한다
Person 클래스에는 deinit도 있는데 클래스 인스턴스의 해제 때 메시지를 print 한다
그 다음 코드에서 정의하는 세 개의 Person? 타입 변수는,
뒤이은 코드에 있는 새로운 Person 인스턴스에 대한 다중 참조를 설정하는데 사용한다
해당 변수들은 Person이 아닌, Person? 이라는 옵셔널 타입이기 때문에 자동으로 nil 값으로 초기화하며,
Person 인스턴스를 현재는 참조하지 않는다
reference1 =Person(name:"John Appleseed")// Prints "John Appleseed is being initialized"
Person 클래스의 이니셜라이저를 호출하는 시점에 "John Appleseed is being initialized" 라는 메시지를 print 한다는 걸 보자
이는 초기화가 일어났 걸 확정한다
새로운 Person 인스턴스를 reference1 변수에 할당했기 때문에, 이제 reference1 에서 새 Person 인스턴스로의 강한 참조가 있다
적어도 하나의 강한 참조가 있기 때문에, ARC는 이 Person 인스턴스를 메모리에 유지하며 해제되지 않도록 한다
동일한 Person 인스턴스를 두 변수에 더 할당하면, 그 인스턴스로의 강한 참조 두 개를 더 구성하게 된다
reference2 = reference1
reference3 = reference1
이제 단일 Person 인스턴스로의 강한 참조가 유지 중이다
두 개의 변수에 nil 을 할당하여 이러한 강한 참조 중 두 개를 끊는다면(원본 참조를 포함하여),
단일한 강한 참조가 남아 있어 Person 인스턴스를 해제하지 않는다
reference1 =nil
reference2 =nil
ARC는 세 번째 최종 강한 참조를 끊기 전까지 Person 인스턴스를 해제하지 않는데,
끊는 시점은 더 이상 Person 인스턴스를 사용하지 않는다는게 명확하다
reference3 =nil// Prints "John Appleseed is being deinitialized"
클래스 인스턴스 사이의 강한 순환 참조 (Strong Reference Cycles Between Class Instances)
위 예제에서, ARC는 새로 생성한 Person 인스턴스의 참조 개수를 추적하고
더 이상 필요하지 않을 땐 그 Person 인스턴스를 해제하는게 가능하다
하지만, 작성한 코드에서 클래스 인스턴스의 강한 참조가 절대로 0개가 되지 않을 가능성이 있다
이는 클래스 인스턴스 두 개가 서로의 강한 참조를 쥐고 있어, 각각의 인스턴스가 다른 인스턴스를 살아있게 하면 발생할 수 있다
이를 강한 순환 참조(strong reference cycle)라 한다
우연히 강한 순환 참조를 생성할 수 있는 예제가 있다
해당 예제는 Person과 Apartment라는 두 클래스를 정의하는데, 이들은 아파트 및 거주자를 모델링한다
classPerson{letname:Stringinit(name:String){self.name = name }varapartment:Apartment?deinit{print("\(name) is being deinitialized")}}classApartment{letunit:Stringinit(unit:String){self.unit = unit }vartenant:Person?deinit{print("Apartment \(unit) is being deinitialized")}}
모든 Person 인스턴스에는 String 타입의 name 프로퍼티와 nil 로 초기화한 옵셔널 apartment 프로퍼티가 있다
apartment 프로퍼티가 옵셔널인 건, 사람이 항상 아파트를 가지진 않기 때문이다
이와 비슷하게, 모든 Apartment 인스턴스에는 String 타입의 unit 프로파티와 nil 로 초기화한 옵셔널 tenant 프로파티가 있다
입주자 tenant 프로퍼티가 옵셔널인 건 아파트에 입주자가 항상 있는 건 아니기 때문이다
두 클래스 모두 deinit를 정의하여, 그 클래스 인스턴스가 정리 중이라는 사실도 print 한다
이는 Person과 Apartment 인스턴스가 예상대로 해제되는지 확인할 수 있게 해준다
다음 코드는 john 과 unit4A 라는 옵셔널 타입 변수를 두 개 선언하는데,
나중에 특정 Apartment 및 Person 인스턴스로 할당을 할 자리이다
두 변수 모두, 옵셔널인 덕에, nil 이라는 초기 값을 가진다
varjohn:Person?varunit4A:Apartment?
이제 특정한 Person 인스턴스 및 Apartment 인스턴스를 생성하고 새 인스턴스를 john 및 unit4A 변수에 할당할 수 있다:
john =Person(name:"John Appleseed")
unit4A =Apartment(unit:"4A")
두 인스턴스를 생성하고 할당한 후의 강한 참조는 이렇게 보인다
이제 john 변수에는 새로운 Person 인스턴스의 강한 참조가 있고,
unit4A 변수에는 새로운 Apartment 인스턴스의 강한 참조가 있다
이제 두 인스턴스를 서로 이어서 사람은 아파트를 가지고, 아파트는 입주자를 가지게 할 수 있다
느낌표(!) 를 써서 john 과 unit4A 옵셔널 변수 안에 저장된 인스턴스의 포장을 풀어서 접근해야,
해당 인스턴스의 프로퍼티를 설정할 수 있다는 걸 유의하자
john!.apartment = unit4A
unit4A!.tenant = john
두 인스턴스를 서로 이은 후의 강한 참조는 이렇게 보인다
불행하게도, 두 인스턴스를 잇는 건 이들 사이에 강한 순환 참조를 생성한다
이제 Person 인스턴스에는 Apartment 인스턴스로의 강한 참조가 있고,
Apartment 인스턴스에는 Person 인스턴스로의 강한 참조가 있다
그러므로, john 과 unit4A 변수가 쥐고 있는 강한 참조를 끊을 때,
참조 개수는 0 으로 떨어지지 않으며, ARC는 인스턴스를 해제하지 않는다
john =nil
unit4A =nil
두 변수에 nil 을 설정할 땐 어느 deinit도 호출하지 않는다는 걸 알 수 있다
강한 순환 참조는 Person과 Apartment 인스턴스의 해제를 늘 막아서, 앱의 메모리가 새어 나가게 한다
john 과 unit4A 변수를 nil 로 설정한 후의 강한 참조는 이렇게 보인다
Person 인스턴스와 Apartment 인스턴스 사이엔 강한 참조가 남아 있으며 끊을 수 없다
클래스 인스턴스 사이의 강한 순환 참조 해결하기 (Resolving Strong Reference Cycles Between Class Instances)
클래스 타입의 프로퍼티와 작업할 때 스위프트는 두 가지 방법을 제공하여 강한 순환 참조를 해결하는데
약한(weak) 참조와 소유하지 않는(unowned) 참조가 있다
weak와 unowned를 활용하여 참조 순환에서 한 인스턴스가 다른 인스턴스를 강하게 쥐지 않고 참조할 수 있게 한다
그러면 강한 순환 참조의 생성 없이 인스턴스가 서로를 참조할 수 있다
다른 인스턴스의 수명이 더 짧을 때-즉, 다른 인스턴스를 먼저 해제할 수 있을 때-약한 참조를 사용한다
위의 Apartment 예제에서, 아파트 수명 중 어떠한 시점에 입주자가 없는게 가능하기에, 이런 경우 약한(weak) 참조로 참조 순환을 끊는 게 적절하다
이와 대조적으로, 다른 인스턴스의 수명이 똑같거나 더 길 땐 소유하지 않는(unowned) 참조를 사용한다
1) 약한 참조 (Weak References)
약한 참조 (weak reference) 는 자신이 참조한 인스턴스를 강하게 쥐지 않는 참조라서,
ARC가 참조 인스턴스를 메모리 해제처리하는 걸 멈추지 않는다
이런 동작은 참조가 강한 순환 참조의 일부가 되는 걸 막는다
약한 참조라고 지시하려면 속성이나 변수 선언 앞에 weak 키워드를 쓰면 된다
약한 참조는 자신이 참조할 인스턴스를 강하게 쥐지 않기 때문에,
여전히 약한 참조가 참조하고 있는 동안에도 그 인스턴스를 해제할 가능성이 있다
그러므로, ARC는 참조 인스턴스를 해제할 때 자동으로 약한 참조를 nil로 설정하게 된다
그리고, 약한 참조의 값은 실행 시간에 nil 로 바뀔 필요가 있기 때문에, 항상 옵셔널 타입이며, 상수 보단 변수로 선언한다
다른 옵셔널 값과 마찬가지로 약한 참조에 값이 있는지 확인할 수 있으며,
더 이상 존재하지 않는 경우, 잘못된 인스턴스에 대해 참조는 불가능하다
ARC가 약한 참조를 nil 로 설정할 땐 프로퍼티 옵저버를 호출하지 않는다
아래 예제는 Person 및 Apartment 예제와 완전히 똑같지만, 한 가지 중요한 차이점이 있다
이번에는, Apartment 타입의 tenant 프로퍼티를 약한 참조로 선언한다
classPerson{letname:Stringinit(name:String){self.name = name }varapartment:Apartment?deinit{print("\(name) is being deinitialized")}}classApartment{letunit:Stringinit(unit:String){self.unit = unit }
weak vartenant:Person?deinit{print("Apartment \(unit) is being deinitialized")}}
john 과 unit4A 라는 두 변수의 강한 참조와 두 인스턴스 사이의 연결 고리는 이전 처럼 생성된다
varjohn:Person?varunit4A:Apartment?
john =Person(name:"John Appleseed")
unit4A =Apartment(unit:"4A")
john!.apartment = unit4A
unit4A!.tenant = john
두 인스턴스를 서로 이은 참조는 이제 이렇게 보인다
Person 인스턴스에는 여전히 Apartment 인스턴스로의 강한 참조가 있지만,
Apartment 인스턴스에는 이제 Person 인스턴스로의 약한(weak) 참조가 있다
이는 nil 을 설정하여 john 변수가 쥔 강한 참조를 끊을 때, Person 인스턴스로의 강한 참조는 더 이상은 없다는 의미가 된다
john =nil// Prints "John Appleseed is being deinitialized"
Person 인스턴스로의 강한 참조가 더 이상 없기 때문에, 이를 해제하고 tenant 프로퍼티는 nil 로 설정한다
유일하게 남은 Apartment 인스턴스로의 강한 참조는 unit4A 변수에 있다
해당 강한 참조를 끊으면, Apartment 인스턴스로의 강한 참조도 더 이상 없게 된다
unit4A =nil// Prints "Apartment 4A is being deinitialized"
Apartment 인스턴스로의 강한 참조가 더 이상 없기 때문에, 이것도 해제된다
가비지 컬렉션을 사용하는 시스템에서는 간단한 캐싱 메커니즘을 구현하기 위해 약한 포인터가 사용되기도 하는데,
이는 메모리 압력이 가비지 컬렉션을 트리거할 때만 강한 참조가 없는 객체가 할당 해제되기 때문이다
위키피디아의 가비지 컬렉션 (컴퓨터 과학) 항목을 참고하자
하지만, ARC 에선, 마지막 강한 참조를 제거하자마자 곧바로 값을 해제하므로, 약한 참조는 이러한 목적에는 적합하지 않다
ARC에선 약한 참조를 ‘단순 임시 저장 구조 (simple caching mechanism)’ 를 만드는 용도로는 사용하지 않는다
2) 소유하지 않는 참조 (Unowned References)
약한 참조와 같이, 소유하지 않는 참조 (unowned reference)는 자신이 참조한 인스턴스를 강하게 쥐지 않는다
하지만, 약한 참조와 달리, 소유하지 않는 참조는 다른 인스턴스와 수명이 똑같거나 더 길 때 사용한다
소유하지 않는 참조라고 지시하려면 속성이나 변수 선언 앞에 unowned 키워드를 작성한다
약한 참조와 달리, 소유하지 않는 참조엔 값이 항상 있을 거라고 예상한다
그 결과, 소유하지 않는 값으로 표시하면 이를 옵셔널로 만들지 않으며,
ARC는 소유하지 않는 참조의 값을 절대 nil 로 설정하지 않는다
참조가 항상(always) 해제되지 않은 인스턴스를 참조하는게 확실할 때만 소유하지 않는 참조를 사용한다
해당 인스턴스를 해제한 후에 소유하지 않는 참조의 값에 접근하려 하면 런타임 에러가 발생한다
다음 예제는 Customer 와 CreditCard 라는, 두 개의 클래스를 정의하는데,
은행 고객 및 그 고객의 신용 카드를 모델링한다
두 클래스는 각각 다른 클래스의 인스턴스를 프로퍼티로 저장한다
이러한 관계는 잠재적으로 강한 순환 참조를 생성할 수도 있다
Customer 와 CreditCard 사이의 관계는 위에 있는 약한 참조 예제의 Apartment 와 Person 사이의 관계와 살짝 다르다
해당 데이터 모델에서, 고객에겐 신용 카드가 있을 수도 없을 수도 있지만, 신용 카드는 고객과 항상(always) 결합되어야 한다
CreditCard 인스턴스는 절대 자신이 참조한 Customer 보다 오래 살지 않는다
이를 표현하기 위해, Customer 클래스엔 옵셔널 card 프로퍼티가 있지만,
CreditCard 클래스엔 소유하지 않는 그리고 옵셔널이 아닌 customer 프로퍼티가 있다
나아가, number 값과 Custom 인스턴스를 자신의 CreditCard 이니셜라이저에 전달해야만
새로운 CreditCard 인스턴스를 생성할 수 있다
이는 CreditCard 인스턴스를 생성할 때, CreditCard 인스턴스와 Customer 인스턴스가 항상 결합된다는 걸 보장한다
신용 카드엔 고객이 항상 있을거기 때문에, 자신의 customer 프로퍼티를 소유하지 않는 참조로 정의하여, 강한 순환 참조를 피한다
classCustomer{letname:Stringvarcard:CreditCard?init(name:String){self.name = name
}deinit{print("\(name) is being deinitialized")}}classCreditCard{letnumber:UInt64
unowned letcustomer:Customerinit(number:UInt64, customer:Customer){self.number = number
self.customer = customer
}deinit{print("Card #\(number) is being deinitialized")}}
CreditCard 클래스의 number 속성은 Int 보단 UInt64 타입으로 정의하여,
number 프로퍼티의 용량이 32-비트 및 64-비트 시스템 양 쪽에서 16-자리 카드 번호를 저장하기에 충분히 크도록 보장한다
다음 코드는 john 이라는 옵셔널 Customer 변수를 정의하는데,
이를 사용하여 특정 고객으로의 참조를 저장한다
변수는, 옵셔널인 덕에, ‘nil’ 이라는 초기 값을 가진다
varjohn:Customer?
이제 Customer 인스턴스를 생성하고, 해당 인스턴스를 그 고객의 card 프로퍼티로 초기화하고 할당할 수 있다
john =Customer(name:"John Appleseed")
john!.card =CreditCard(number:1234_5678_9012_3456, customer: john!)
이제 두 인스턴스를 이은 후의, 참조는 이렇게 보인다
그림을 보면 ‘소유하지 않는 참조 (unowned reference)’ 라는 이름의 의미를 알 수 있다
고객은 신용 카드를 소유하지만, 신용 카드는 고객을 소유하지 않는다
실제 세계에 빗대어 보면, 고객은 신용 카드를 바꿀 수 있지만 신용 카드는 고객을 바꿀 수 없다
그러므로, 외부에서 신용 카드를 직접 참조하는 변수도 없다
이제 Customer 인스턴스엔 CreditCard 인스턴스로의 강한 참조가 있고,
CreditCard 인스턴스엔 Customer 인스턴스로의 소유하지 않는 참조가 있다
소유하지 않는 customer 참조로 인하여 john 변수가 쥔 강한 참조를 끊을 때,
Customer 인스턴스로의 강한 참조는 더 이상 존재하지 않는다
Customer 인스턴스로의 강한 참조가 더 이상 없기 때문에 이를 메모리에서 해제한다
이게 발생한 후엔, CreditCard 인스턴스로의 강한 참조도 더 이상 없으므로, 이것도 해제한다
john =nil// Prints "John Appleseed is being deinitialized"// Prints "Card #1234567890123456 is being deinitialized"
마지막 코드는 john 변수를 nil 로 설정한 후엔
Customer 인스턴스와 CreditCard 인스턴스 deinit이 둘 다 자신의 “정리 (deinitialized)” 메시지를 print 한다는 걸 보여준다
위 예제는 소유하지 않는 참조를 안전하게 사용하는 방법을 보여준다
Swift는 성능상의 이유 등을 위해 런타임 안전성 검사를 사용하지 않도록 설정해야 하는 경우,
안전하지 않게 소유하지 않는 참조도 제공한다
모든 안전하지 않은 작업처럼 그 코드의 안전성 검사는 개발자가 직접 책임져야 한다
안전하지 않게 소유하지 않는 참조라고 지시하려면 unowned(unsafe) 라고 작성하면 된다
자신이 참조한 인스턴스를 해제한 후에 안전하지 않게 소유하지 않는 참조에 접근하려 하면
프로그램은 인스턴스였던 곳의 메모리에 접근하려고 시도할 건데 이는 안전하지 않은 작업이다
3) 소유하지 않는 옵셔널 참조 (Unowned Optional References)
클래스로의 옵셔널 참조를 소유하지 않는 걸로 표시할 수 있다
ARC 소유권 모델 (ARC ownership model) 관점에선, 동일한 상황에서 소유하지 않는 옵셔널 참조와 약한 참조 둘 다 사용할 수 있다
차이점은 소유하지 않는 옵셔널 참조를 사용할 땐, 항상 유효한 객체를 참조하고 있는지 또는 nil 로 설정한게 확실한지 직접 책임진다는 점이다
학교에서 한 특별한 학과(department)가 제안한 교육 과정(courses) 을 추적하는 예제가 있다
classDepartment{varname:Stringvarcourses:[Course]init(name:String){self.name = name
self.courses =[]}}classCourse{varname:String
unowned vardepartment:Department
unowned varnextCourse:Course?init(name:String, in department:Department){self.name = name
self.department = department
self.nextCourse =nil}}
Department는 학과가 제안한 각각의 교육 과정으로의 강한 참조를 유지한다
ARC 소유권 모델에선, 학과가 자신의 교육 과정을 소유한다
Course에는 두 개의 소유하지 않는 참조가 있으며, 하나는 학과로 향하고 다른 하나는 학생이 그 다음 들어야 할 교육 과정으로 향하는데
교육 과정은 이 두 객체 중 어느 것도 소유하지 않는다
모든 교육 과정은 어떠한 학과의 일부라서 department 프로퍼티는 옵셔널이 아니다
하지만, 일부 교육 과정엔 뒤따라서 추천할 교육 과정이 없기 때문에, nextCourse 프로퍼티는 옵셔널이다
아래는 해당 클래스들을 사용한 예제다
letdepartment=Department(name:"Horticulture")letintro=Course(name:"Survey of Plants", in: department)letintermediate=Course(name:"Growing Common Herbs", in: department)letadvanced=Course(name:"Caring for Tropical Plants", in: department)
intro.nextCourse = intermediate
intermediate.nextCourse = advanced
department.courses =[intro, intermediate, advanced]
위 코드는 한 학과 및 그의 세 교육 과정을 생성한다
입문(intro) 및 중급(intermediate) 둘 다 자신의 nextCourse 프로퍼티에 그 다음으로 제안할 교육 과정을 저장하는데,
이걸 완료한 학생이 들어야 할 교육 과정에 대한 소유하지 않는 옵셔널 참조를 유지한다
소유하지 않는 옵셔널 참조는 자신이 옵셔널로 래핑한 클래스 인스턴스를 강하게 쥐지 않아서, ARC 가 인스턴스를 해제하는 걸 막지 않는다
소유하지 않는 옵셔널 참조는, nil 이 될 수 있다는 것만 제외하면, ARC 밑에서 소유하지 않는 참조와 똑같이 동작한다
옵셔널-아닌 소유하지 않는 참조와 마찬가지로, nextCourse는 항상 해제하지 않은 교과 과정만 참조한다는 보장을 직접 책임져야 한다
이 경우, 예를 들어, department.courses에서 한 교육 과정을 삭제할 땐
다른 교육 과정에 있을지 모를 삭제할 과정으로의 어떤 참조도 제거할 필요가 있다
옵셔널 값의 실제 타입은 Optional 인데, 이는 Swift 표준 라이브러리에 있는 열거형이다
하지만, unowned로 값 타입을 표시할 수 없다는 규칙에서 옵셔널은 예외이다
클래스를 포장한 옵셔널은 참조 카운팅을 사용하지 않아서, 옵셔널로의 강한 참조를 유지할 필요가 없다
소유하지 않는 참조와 암시적으로 언래핑하는 옵셔널 프로퍼티 (Unowned References and Implicitly Unwrapped Optional Properties)
위의 약한 및 소유하지 않는 참조 예제에서 다루는 건
강한 순환 참조를 끊어야하는 좀 더 일반적인 시나리오 두 가지였다
Person과 Apartment 예제는,
둘 다 nil 을 허용한, 두 프로퍼티가 강한 순환 참조를 일으킬 수 있는 상황을 보여준다
이런 시나리오의 해결엔 약한 참조가 적합하다
Customer와 CreditCard 예제는,
nil 을 허용한 프로퍼티 하나와 nil 일 수 없는 또 다른 프로퍼티가 강한 순환 참조를 일으킬 수 있는 상황을 보여준다
이런 시나리오의 해결엔 소유하지 않는 참조가 적합하다
하지만, 프로퍼티 둘 다 (both) 값이 항상 있어야 하고,
초기화를 한 번 완료하면 어떤 프로퍼티도 nil 이면 안되는, 세 번째 시나리오가 있다
해당 시나리오에선, 한 클래스의 소유하지 않는 프로퍼티와 다른 클래스의 암시적으로 언래핑하는 옵셔널 속성을 조합하는 게 적합하다
이는 초기화를 한 번 완료하면 옵셔널 언래핑 없이 프로퍼티 둘 다에 직접 접근하면서도, 순환 참조를 피할 수 있게 한다
이번 챕터는 그런 관계를 설정하는 방법을 보여준다
아래 예제는, Country와 City 라는 두 클래스를 정의하는데,
각각 프로퍼티로 서로의 클래스 인스턴스를 저장한다
해당 데이터 모델에서, 모든 국가엔 반드시 항상 수도가 있어야 하고, 모든 도시는 반드시 항상 국가에 소속돼야 한다
이를 나타내기 위해, Country 클래스엔 capitalCity 프로퍼티가 있고, City 클래스엔 country 프로퍼티가 있다
classCountry{letname:StringvarcapitalCity:City!init(name:String, capitalName:String){self.name = name
self.capitalCity =City(name: capitalName, country:self)}}classCity{letname:String
unowned letcountry:Countryinit(name:String, country:Country){self.name = name
self.country = country
}}
두 클래스 사이에 상호 의존성을 설정하기 위해,
City 이니셜라이저는 Country 인스턴스를 취하고, 이 인스턴스를 자신의 country 프로퍼티에 저장한다
City 이니셜라이저는 Country 초기자 안에서 호출한다
하지만, Two-Phase Initialization에서 설명한 것처럼,
새 Country 인스턴스 완전히 초기화하기 전까진 Country 이니셜라이저가 self 를 City 이니셜라이저에 전달할 수 없다
이 필수 조건에 대처하려면, Country의 capitalCity 프로퍼티를 암시적으로 언래핑하는 옵셔널 속성으로 선언하고자,
자신의 타입 보조 설명 끝에 느낌표를 붙여 City!라고 지시한다
이는, 다른 어떤 옵셔널 같이, capitalCity 속성도 nil 이라는 기본 값을 가지지만, Implicitly Unwrapped Optionals에서 설명한 것처럼 값을 언래핑하지 않고도 접근 할 수 있다는 의미다
capitalCity에는 기본 값 nil 이 있기 때문에,
Country 인스턴스가 이니셜라이저 안에서 자신의 name 프로퍼티를 설정하자마자
곧 새로운 Country 인스턴스는 완전히 초기화되는 걸로 고려한다
이는 name 프로퍼티를 설정하자마자 곧 Country 이니셜라이저가 암시적 self 프로퍼티의 참조와 전달을 시작할 수 있다는 의미이다
그리하여 Country 이니셜라이저가 자신만의 capitalCity 프로퍼티를 설정할 때
Country 이니셜라이저가 City 이니셜라이저의 매개 변수로 self를 전달할 수 있게된다
모든 게 의미하는 건, 강한 순환 참조의 생성 없이 Country와 City 인스턴스를 단일 구문으로 생성할 수 있으며,
느낌표로 자신의 옵셔널 값을 언래핑 할 필요 없이, 직접 capitalCity 프로퍼티에 접근할 수 있다는 의미다
varcountry=Country(name:"Canada", capitalName:"Ottawa")print("\(country.name)'s capital city is called \(country.capitalCity.name)")// Prints "Canada's capital city is called Ottawa"
위 예제에서, 암시적으로 언래핑하는 옵셔널을 사용하는 의미는
2단계 클래스 이니셜라이저의 모든 필수 조건을 만족한다는 거다
capitalCity 프로퍼티는 초기화를 한 번 완료하면 옵셔널-아닌 값처럼 사용하고 접근할 수 있으면서도 강한 순환 참조도 피할 수 있다
클로저의 강한 순환 참조
위에서 두 클래스 인스턴스 프로퍼티가 서로의 강한 참조를 쥘 때
강한 순환 참조가 생성될 수 있는 경우를 봤다
또한, 약한 및 소유하지 않는 참조를 사용하여 강한 순환 참조를 끊는 방법도 봤다
클래스 인스턴스의 프로퍼티에 클로저를 할당하고, 해당 클로저 본문은 인스턴스(self)를 캡쳐 경우에도 강한 순환 참조가 일어날 수 있다
캡쳐는, self.someProperty 같이, 클로저 본문이 인스턴스의 프로퍼티에 접근하거나,
self.someMethod() 같이, 클로저가 인스턴스의 메소드를 호출할 때 일어날 수 있다
어느 경우든, 이러한 접근은 클로저가 self 를 “캡쳐” 하여, 강한 순환 참조를 생성한다
이런 강한 순환 참조는 클로저가, 클래스 같은, 참조 타입 (reference type)이기 때문에 발생한다
프로퍼티에 클로저를 할당할 땐, 해당 클로저로 참조(reference)를 할당하고 있다
본질적으로, 이는 위와 동일한 문제인 두 개의 강한 참조가 서로 살아 있도록 유지된다
하지만, 두 클래스 인스턴스라기 보단, 이번에는 클래스 인스턴스와 클로저의 강한 참조가 서로 살아 있도록 유지된다
Swift는 문제의 풀이법으로, 클로저 캡쳐 리스트를 제공한다
하지만, 클로저 캡쳐 리스트로 강한 순환 참조를 끊는 방법을 배우기 전에,
그런 순환이 일어나는 원인을 이해햐는게 중요하다
아래 예제는 self 를 참조한 클로저를 사용할 때
강한 순환 참조를 생성할 수 있는 경우를 보여준다
예제는 HTMLElement 라는 클래스를 정의하여, HTML 문서 안의 개별 원소를 단순하게 모델링한다
classHTMLElement{letname:Stringlettext:String?
lazy varasHTML:()->String={
if let text =self.text {return"<\(self.name)>\(text)</\(self.name)>"}else{return"<\(self.name) />"}}init(name:String, text:String?=nil){self.name = name
self.text = text
}deinit{print("\(name) is being deinitialized")}}
HTMLElement 클래스는 name 프로퍼티를 정의하여,
제목 원소면 "h1", 문단 원소면 "p", 줄 끊음 원소면 "br" 같은, 원소 이름을 지시한다
HTMLElement 는 옵셔널 text 프로퍼티도 정의하는데,
여기에 문자열을 설정하면 그 HTML 원소 안에 그릴 텍스트를 나타낼 수 있다
단순한 두 프로퍼티에 더해, HTMLElement 클래스는 asHTML 이라는 lazy 프로퍼티도 정의한다
해당 프로퍼티는 name 과 text 를 HTML 문자열 조각으로 조합하는 클로저를 참조한다
asHTML 프로퍼티의 타입은 () -> String, 또는 “매개 변수가 없고, String 값을 반환하는 함수” 다
기본적으로, asHTML 프로퍼티에 할당한 클로저는 HTML 태그를 문자열로 나타낸 걸 반환한다
태그는 text 가 존재하면 그 옵셔널 값을, 존재하지 않으면 아무런 텍스트 내용물도 담지 않는다
문단 원소 "p"이면, text 프로퍼티가 "some text" 또는 nil 여부에 따라, 클로저가 "<p>some text</p>" 나 <p /> 를 반환하게 된다
asHTML 프로퍼티의 이름과 사용법은 어느 정도 인스턴스 메소드와 비슷하다
하지만, asHTML은 인스턴스 메소드라기 보단 클로저 프로퍼티이기 때문에,
만약 특정 HTML 요소의 렌더링을 변경하려는 경우, asHTML 프로퍼티의 기본 값을 커스텀 클로저로 교체할 수 있다
예를 들어, 빈 HTML 태그를 반환하지 방지하기 위해
text 프로퍼티가 nil이면 어떠한 텍스트로 기본 설정되는 클로저를 asHTML 프로퍼티에 설정할 수 있다
asHTML 프로퍼티를 lazy로 선언한 건, 실제로 어떠한 HTML 출력 대상에 문자열 값을 그릴 필요가 있을 때만 원소가 필요하기 때문이다
asHTML가 lazy 프로퍼티란 사실은 기본 클로저 안에서 self 를 참조할 수 있다는 의미인데,
초기화를 완료하여 self 의 존재를 알기 전까진 lazy 프로퍼티에 접근하지 않을 것이기 때문이다
HTMLElement 클래스가 제공한 단일 이니셜라이저는,
name 매개 변수와 원할 경우 새 원소를 초기화하는 text 매개 변수를 가진다
클래스는 deinit도 정의하는데, 이는 HTMLElement 인스턴스가 해제할 때를 보여주는 메시지를 print 한다
HTMLElement 클래스로 새로운 인스턴스를 생성하고 출력하는 방법은 이렇다
varparagraph:HTMLElement?=HTMLElement(name:"p", text:"hello, world")print(paragraph!.asHTML())// Prints "<p>hello, world</p>"
위의 paragraph 변수를 옵셔널 (optional) HTMLElement 로 정의하여,
아래에서 nil 로 설정할 수 있어서 강한 순환 참조가 있다는 걸 실증할 수 있다
불행하게도, 위에서 작성한, HTMLElement 클래스는 HTMLElement 인스턴스와
자신의 기본 asHTML 값이 사용한 클로저 사이에 강한 순환 참조를 생성한다
인스턴스의 asHTML 프로퍼티은 자신의 클로저로 강한 참조를 쥔다
하지만, 클로저가 self.name 과 self.text를 참조하는 식으로 자신의 본문 안에서 self 를 참조하기 때문에,
클로저가 ‘self’ 를 캡쳐하며, 이는 HTMLElement 인스턴스로의 강한 참조를 되돌려 쥔다는 걸 의미한다
둘 사이에 강한 순환 참조가 생성된다
클로저의 캡쳐링에 대한 더 많은 정보는, Capturing Values 페이지를 보자
클로저가 self 를 여러 번 참조할지라도, HTMLElement 인스턴스로의 강한 참조는 하나만 붙잡는다
paragraph 변수에 nil 을 설정하여 HTMLElement 인스턴스로의 강한 참조를 끊으면,
강한 순환 참조 때문에, HTMLElement 인스턴스나 자신의 클로저 어느 것도 해제되지 않는다
paragraph =nil
HTMLElement deinit 안의 메시지를 print 하지 않아, HTMLElement 인스턴스가 메모리에서 해제되지 않음을 보여준다
클로저의 강한 순환 참조 해결하기 (Resolving Strong Reference Cycles for Closures)
클로저와 클래스 인스턴스 사이의 강한 순환 참조를 해결하려면
클로저 정의 부분에 캡쳐 리스트를 정의하면 된다
캡쳐 리스트는 클로저 본문 안에서 하나 이상의 참조 타입을 붙잡을 때 사용할 규칙을 정의한다
두 클래스 인스턴스 사이의 강한 순환 참조처럼, 각각의 붙잡을 참조를 강한 참조 보단 약한 또는 소유하지 않는 참조로 선언한다
약한 또는 소유하지 않음을 적절하게 선택하는 건 다른 코드와의 관계에 달려 있다
스위프트는 클로저 안에서 self 의 멤버를 참조할 때마다
그냥 someProperty 나 someMethod() 라고 하기 보단 self.someProperty 나 self.someMethod() 라고 작성하길 요구한다
이는 우연히 self를 캡쳐할 가능성이 있음을 떠올리게 한다
1) 캡쳐 리스트 정의하기 (Defining a Capture List)
캡쳐 리스트의 각 항목은 weak 또는 unowned 키워드와 self 같은 클래스 인스턴스
또는 delegate = self.delegate 같이 어떠한 값으로 초기화한 변수로의 참조가 쌍을 이룬다
이 쌍들은 한 쌍의 대괄호 안에 쉼표로 구분하여 작성한다
클로저가 콘텍스트로 추론될 수 있기 때문에 매개 변수 목록이나 반환 타입을 지정하지 않으면,
캡쳐 리스트를 클로저 시작 부분에 두고, 그 뒤에 in 키워드가 따라온다
lazy varsomeClosure={[unowned self, weak delegate =self.delegate]in// closure body goes here}
사실, 두 경우 모두 캡쳐 리스트가 클로저 본문 가장 앞에 있다
그러므로 캡쳐 리스트는 클로저 본문 맨 앞에 둔다라고 생각하면 된다
2) 약한 및 소유하지 않는 참조 (Weak and Unowned References)
클로저와 캡쳐할 인스턴스가 항상 서로를 참조하며, 항상 동시에 해제할 땐 클로저 안의 캡쳐 리스트를 소유하지 않는 참조로 정의한다
거꾸로 말해서, 캡쳐한 참조가 미래의 어떠한 시점에 nil이 될 수도 있을 땐 캡쳐를 약한 참조로 정의한다
약한 참조는 항상 옵셔널 타입이며, 자신이 참조한 인스턴스를 해제할 땐 자동으로 nil이 된다
이는 클로저 본문 안에서 인스턴스 자신이 존재하는지 검사할 수 있게 한다
캡쳐한 참조가 절대 nil 이 되지 않을거면, 약한 참조 보단, 항상 소유하지 않는 참조로 캡쳐해야 한다
위의 Strong Reference Cycles for Closures에 있는 HTMLElement 예제의 강한 순환 참조를 해결하는데는
소유하지 않는 참조가 사용하기 적절한 캡쳐 방법이 된다
다음은 순환을 피하는 HTMLElement 클래스의 작성 방법이다
classHTMLElement{letname:Stringlettext:String?
lazy varasHTML:()->String={[unowned self]in
if let text =self.text {return"<\(self.name)>\(text)</\(self.name)>"}else{return"<\(self.name) />"}}init(name:String, text:String?=nil){self.name = name
self.text = text
}deinit{print("\(name) is being deinitialized")}}
해당 HTMLElement 구현은, asHTML 클로저 안에서 캡쳐 리스트의 추가만 제외하면, 이전 구현과 완전히 똑같다
이 경우, 캡쳐 리스트는 [unowned self] 인데, 이는 “‘self’ 를 강한 참조 보단 소유하지 않는 참조로 붙잡아라” 는 의미다
HTMLElement 인스턴스의 생성과 print는 이전 처럼 할 수 있다
varparagraph:HTMLElement?=HTMLElement(name:"p", text:"hello, world")print(paragraph!.asHTML())// Prints "<p>hello, world</p>"
캡처 리스트가 제자리에 있는 참조는 이렇게 보인다
이번에, 클로저가 캡쳐한 self는 소유하지 않는 참조라, 자신이 캡쳐한 HTMLElement 인스턴스를 강하게 쥐지 않는다
paragraph 변수로부터의 강한 참조에 nil 을 설정하면, 아래 예제에서 deinit 메시지를 print 하는 걸로 볼 수 있듯이,
HTMLElement 인스턴스를 메모리에서 해제하게 된다
paragraph =nil// Prints "p is being deinitialized"
Swift는 자동 참조 카운팅(Automatic Reference Counting; ARC) 을 사용하여 앱의 메모리 사용을 추적하고 관리한다
대부분의 경우, 이는 Swift에서 메모리 관리는 “그냥 작동하는 (just works)” 것이며, 메모리 관리를 직접 생각할 필요가 없다
ARC는 클래스 인스턴스가 더 이상 필요하지 않을 때 해당 인스턴스가 사용한 메모리를 자동으로 해제한다
하지만, 몇몇 경우에 ARC 는 메모리 관리를 위해 코드 간의 관계에 대한 더 많은 정보를 요구한다
이번 챕터는 그 상황들을 설명하며 ARC 가 앱의 모든 메모리를 관리하게 해주는 방법을 보여준다
Swift에서 ARC 를 사용하는 건 Transitioning to ARC Release Notes에서 설명한 오브젝티브-C 에서 ARC 를 사용하는 접근법과 아주 비슷하다
참조 카운팅은 클래스 인스턴스에만 적용된다
구조체와 열거체는 값 타입이지, 참조 타입이 아니라서, 참조로 저장하고 전달하지 않는다
ARC의 작동 방식
새로운 클래스 인스턴스를 생성할 때마다, ARC 가 메모리 덩어리를 할당하여 그 인스턴스에 대한 정보를 저장한다
해당 메모리는 인스턴스의 타입에 대한 정보와, 그 인스턴스와 관계된 어떤 저장 프로퍼티의 값을 함께 가지고 있다
추가적으로, 더 이상 인스턴스가 필요없을 땐, ARC가 그 인스턴스에서 사용한 메모리를 해제하여
메모리를 대신 다른 용도로 사용할 수 있게 한다
이는 더 이상 클래스 인스턴스가 필요하지 않을 때, 이들이 메모리 공간을 차지하게 않도록 보장한다
하지만, ARC가 해제한 인스턴스가 여전히 사용 중인 거라면,
더 이상 그 인스턴스의 프로퍼티에 접근하거나,
그 인스턴스의 메소드를 호출하는 게 불가능해진다
진짜로, 인스턴스에 접근하려 시도하면, 앱이 크래쉬를 일으켜 종료된다
인스턴스가 아직 필요한 동안에는 사라지지 않도록 하기 위해,
ARC는 현재 얼마나 많은 프로퍼티, 상수, 및 변수가 각각의 클래스 인스턴스를 참조하고 있는지 추적한다
ARC는 해당 인스턴스에 적어도 하나의 활동 중인 참조가 존재하는 한 인스턴스를 해제하지 않게 된다
이를 가능하게 하려고, 프로퍼니나, 상수, 또는 변수에 클래스 인스턴스를 할당할 때마다
해당 프로퍼티나, 상수, 또는 변수는 인스턴스에 대한 강한 참조 (strong reference)를 만든다
참조를 “강한 (strong)” 참조라고 하는 건 그 인스턴스를 꽉 쥐고 있기 때문이며, 강한 참조가 남아 있는 한 메모리 해제를 허용하지 않는다
ARC 의 실제 사례 (ARC in Action)
아래는 자동 참조 카운팅의 작동 방법에 대한 예제이다
예제는 Person 이라는 단순한 클래스로 시작하는데, 이는 name 이라는 상수 저장 프로퍼티를 정의한다
Person 클래스에 있는 이니셜라이저는 인스턴스의 name 프로퍼티를 설정하고 초기화가 진행중이라는 메시지를 print 한다
Person 클래스에는 deinit도 있는데 클래스 인스턴스의 해제 때 메시지를 print 한다
그 다음 코드에서 정의하는 세 개의 Person? 타입 변수는,
뒤이은 코드에 있는 새로운 Person 인스턴스에 대한 다중 참조를 설정하는데 사용한다
해당 변수들은 Person이 아닌, Person? 이라는 옵셔널 타입이기 때문에 자동으로 nil 값으로 초기화하며,
Person 인스턴스를 현재는 참조하지 않는다
이제 새로운 Person 인스턴스를 생성하여 세 변수 중 하나에 할당할 수 있다
Person 클래스의 이니셜라이저를 호출하는 시점에 "John Appleseed is being initialized" 라는 메시지를 print 한다는 걸 보자
이는 초기화가 일어났 걸 확정한다
새로운 Person 인스턴스를 reference1 변수에 할당했기 때문에, 이제 reference1 에서 새 Person 인스턴스로의 강한 참조가 있다
적어도 하나의 강한 참조가 있기 때문에, ARC는 이 Person 인스턴스를 메모리에 유지하며 해제되지 않도록 한다
동일한 Person 인스턴스를 두 변수에 더 할당하면, 그 인스턴스로의 강한 참조 두 개를 더 구성하게 된다
이제 단일 Person 인스턴스로의 강한 참조가 유지 중이다
두 개의 변수에 nil 을 할당하여 이러한 강한 참조 중 두 개를 끊는다면(원본 참조를 포함하여),
단일한 강한 참조가 남아 있어 Person 인스턴스를 해제하지 않는다
ARC는 세 번째 최종 강한 참조를 끊기 전까지 Person 인스턴스를 해제하지 않는데,
끊는 시점은 더 이상 Person 인스턴스를 사용하지 않는다는게 명확하다
클래스 인스턴스 사이의 강한 순환 참조 (Strong Reference Cycles Between Class Instances)
위 예제에서, ARC는 새로 생성한 Person 인스턴스의 참조 개수를 추적하고
더 이상 필요하지 않을 땐 그 Person 인스턴스를 해제하는게 가능하다
하지만, 작성한 코드에서 클래스 인스턴스의 강한 참조가 절대로 0개가 되지 않을 가능성이 있다
이는 클래스 인스턴스 두 개가 서로의 강한 참조를 쥐고 있어, 각각의 인스턴스가 다른 인스턴스를 살아있게 하면 발생할 수 있다
이를 강한 순환 참조(strong reference cycle)라 한다
강한 순환 참조를 해결하려면 클래스 사이의 일부 관계를 강한 참조 대신 약한(weak) 또는 소유하지 않는(unowned) 참조로 정의하면 된다
이 과정은 Resolving Strong Reference Cycles Between Class Instances 페이지에서 설명한다
하지만, 강한 순환 참조의 해결 방법을 배우기 전에, 그런 순환을 유발하는 원인을 이해하는게 중요하다
우연히 강한 순환 참조를 생성할 수 있는 예제가 있다
해당 예제는 Person과 Apartment라는 두 클래스를 정의하는데, 이들은 아파트 및 거주자를 모델링한다
모든 Person 인스턴스에는 String 타입의 name 프로퍼티와 nil 로 초기화한 옵셔널 apartment 프로퍼티가 있다
apartment 프로퍼티가 옵셔널인 건, 사람이 항상 아파트를 가지진 않기 때문이다
이와 비슷하게, 모든 Apartment 인스턴스에는 String 타입의 unit 프로파티와 nil 로 초기화한 옵셔널 tenant 프로파티가 있다
입주자 tenant 프로퍼티가 옵셔널인 건 아파트에 입주자가 항상 있는 건 아니기 때문이다
두 클래스 모두 deinit를 정의하여, 그 클래스 인스턴스가 정리 중이라는 사실도 print 한다
이는 Person과 Apartment 인스턴스가 예상대로 해제되는지 확인할 수 있게 해준다
다음 코드는 john 과 unit4A 라는 옵셔널 타입 변수를 두 개 선언하는데,
나중에 특정 Apartment 및 Person 인스턴스로 할당을 할 자리이다
두 변수 모두, 옵셔널인 덕에, nil 이라는 초기 값을 가진다
이제 특정한 Person 인스턴스 및 Apartment 인스턴스를 생성하고 새 인스턴스를 john 및 unit4A 변수에 할당할 수 있다:
두 인스턴스를 생성하고 할당한 후의 강한 참조는 이렇게 보인다
이제 john 변수에는 새로운 Person 인스턴스의 강한 참조가 있고,
unit4A 변수에는 새로운 Apartment 인스턴스의 강한 참조가 있다
이제 두 인스턴스를 서로 이어서 사람은 아파트를 가지고, 아파트는 입주자를 가지게 할 수 있다
느낌표(!) 를 써서 john 과 unit4A 옵셔널 변수 안에 저장된 인스턴스의 포장을 풀어서 접근해야,
해당 인스턴스의 프로퍼티를 설정할 수 있다는 걸 유의하자
두 인스턴스를 서로 이은 후의 강한 참조는 이렇게 보인다
불행하게도, 두 인스턴스를 잇는 건 이들 사이에 강한 순환 참조를 생성한다
이제 Person 인스턴스에는 Apartment 인스턴스로의 강한 참조가 있고,
Apartment 인스턴스에는 Person 인스턴스로의 강한 참조가 있다
그러므로, john 과 unit4A 변수가 쥐고 있는 강한 참조를 끊을 때,
참조 개수는 0 으로 떨어지지 않으며, ARC는 인스턴스를 해제하지 않는다
두 변수에 nil 을 설정할 땐 어느 deinit도 호출하지 않는다는 걸 알 수 있다
강한 순환 참조는 Person과 Apartment 인스턴스의 해제를 늘 막아서, 앱의 메모리가 새어 나가게 한다
john 과 unit4A 변수를 nil 로 설정한 후의 강한 참조는 이렇게 보인다
Person 인스턴스와 Apartment 인스턴스 사이엔 강한 참조가 남아 있으며 끊을 수 없다
클래스 인스턴스 사이의 강한 순환 참조 해결하기 (Resolving Strong Reference Cycles Between Class Instances)
클래스 타입의 프로퍼티와 작업할 때 스위프트는 두 가지 방법을 제공하여 강한 순환 참조를 해결하는데
약한(weak) 참조와 소유하지 않는(unowned) 참조가 있다
weak와 unowned를 활용하여 참조 순환에서 한 인스턴스가 다른 인스턴스를 강하게 쥐지 않고 참조할 수 있게 한다
그러면 강한 순환 참조의 생성 없이 인스턴스가 서로를 참조할 수 있다
다른 인스턴스의 수명이 더 짧을 때-즉, 다른 인스턴스를 먼저 해제할 수 있을 때-약한 참조를 사용한다
위의 Apartment 예제에서, 아파트 수명 중 어떠한 시점에 입주자가 없는게 가능하기에, 이런 경우 약한(weak) 참조로 참조 순환을 끊는 게 적절하다
이와 대조적으로, 다른 인스턴스의 수명이 똑같거나 더 길 땐 소유하지 않는(unowned) 참조를 사용한다
1) 약한 참조 (Weak References)
약한 참조 (weak reference) 는 자신이 참조한 인스턴스를 강하게 쥐지 않는 참조라서,
ARC가 참조 인스턴스를 메모리 해제처리하는 걸 멈추지 않는다
이런 동작은 참조가 강한 순환 참조의 일부가 되는 걸 막는다
약한 참조라고 지시하려면 속성이나 변수 선언 앞에 weak 키워드를 쓰면 된다
약한 참조는 자신이 참조할 인스턴스를 강하게 쥐지 않기 때문에,
여전히 약한 참조가 참조하고 있는 동안에도 그 인스턴스를 해제할 가능성이 있다
그러므로, ARC는 참조 인스턴스를 해제할 때 자동으로 약한 참조를 nil로 설정하게 된다
그리고, 약한 참조의 값은 실행 시간에 nil 로 바뀔 필요가 있기 때문에, 항상 옵셔널 타입이며, 상수 보단 변수로 선언한다
다른 옵셔널 값과 마찬가지로 약한 참조에 값이 있는지 확인할 수 있으며,
더 이상 존재하지 않는 경우, 잘못된 인스턴스에 대해 참조는 불가능하다
아래 예제는 Person 및 Apartment 예제와 완전히 똑같지만, 한 가지 중요한 차이점이 있다
이번에는, Apartment 타입의 tenant 프로퍼티를 약한 참조로 선언한다
john 과 unit4A 라는 두 변수의 강한 참조와 두 인스턴스 사이의 연결 고리는 이전 처럼 생성된다
두 인스턴스를 서로 이은 참조는 이제 이렇게 보인다
Person 인스턴스에는 여전히 Apartment 인스턴스로의 강한 참조가 있지만,
Apartment 인스턴스에는 이제 Person 인스턴스로의 약한(weak) 참조가 있다
이는 nil 을 설정하여 john 변수가 쥔 강한 참조를 끊을 때, Person 인스턴스로의 강한 참조는 더 이상은 없다는 의미가 된다
Person 인스턴스로의 강한 참조가 더 이상 없기 때문에, 이를 해제하고 tenant 프로퍼티는 nil 로 설정한다
유일하게 남은 Apartment 인스턴스로의 강한 참조는 unit4A 변수에 있다
해당 강한 참조를 끊으면, Apartment 인스턴스로의 강한 참조도 더 이상 없게 된다
Apartment 인스턴스로의 강한 참조가 더 이상 없기 때문에, 이것도 해제된다
2) 소유하지 않는 참조 (Unowned References)
약한 참조와 같이, 소유하지 않는 참조 (unowned reference)는 자신이 참조한 인스턴스를 강하게 쥐지 않는다
하지만, 약한 참조와 달리, 소유하지 않는 참조는 다른 인스턴스와 수명이 똑같거나 더 길 때 사용한다
소유하지 않는 참조라고 지시하려면 속성이나 변수 선언 앞에 unowned 키워드를 작성한다
약한 참조와 달리, 소유하지 않는 참조엔 값이 항상 있을 거라고 예상한다
그 결과, 소유하지 않는 값으로 표시하면 이를 옵셔널로 만들지 않으며,
ARC는 소유하지 않는 참조의 값을 절대 nil 로 설정하지 않는다
다음 예제는 Customer 와 CreditCard 라는, 두 개의 클래스를 정의하는데,
은행 고객 및 그 고객의 신용 카드를 모델링한다
두 클래스는 각각 다른 클래스의 인스턴스를 프로퍼티로 저장한다
이러한 관계는 잠재적으로 강한 순환 참조를 생성할 수도 있다
Customer 와 CreditCard 사이의 관계는 위에 있는 약한 참조 예제의 Apartment 와 Person 사이의 관계와 살짝 다르다
해당 데이터 모델에서, 고객에겐 신용 카드가 있을 수도 없을 수도 있지만, 신용 카드는 고객과 항상(always) 결합되어야 한다
CreditCard 인스턴스는 절대 자신이 참조한 Customer 보다 오래 살지 않는다
이를 표현하기 위해, Customer 클래스엔 옵셔널 card 프로퍼티가 있지만,
CreditCard 클래스엔 소유하지 않는 그리고 옵셔널이 아닌 customer 프로퍼티가 있다
나아가, number 값과 Custom 인스턴스를 자신의 CreditCard 이니셜라이저에 전달해야만
새로운 CreditCard 인스턴스를 생성할 수 있다
이는 CreditCard 인스턴스를 생성할 때, CreditCard 인스턴스와 Customer 인스턴스가 항상 결합된다는 걸 보장한다
신용 카드엔 고객이 항상 있을거기 때문에, 자신의 customer 프로퍼티를 소유하지 않는 참조로 정의하여, 강한 순환 참조를 피한다
다음 코드는 john 이라는 옵셔널 Customer 변수를 정의하는데,
이를 사용하여 특정 고객으로의 참조를 저장한다
변수는, 옵셔널인 덕에, ‘nil’ 이라는 초기 값을 가진다
이제 Customer 인스턴스를 생성하고, 해당 인스턴스를 그 고객의 card 프로퍼티로 초기화하고 할당할 수 있다
이제 두 인스턴스를 이은 후의, 참조는 이렇게 보인다
그림을 보면 ‘소유하지 않는 참조 (unowned reference)’ 라는 이름의 의미를 알 수 있다
고객은 신용 카드를 소유하지만, 신용 카드는 고객을 소유하지 않는다
실제 세계에 빗대어 보면, 고객은 신용 카드를 바꿀 수 있지만 신용 카드는 고객을 바꿀 수 없다
그러므로, 외부에서 신용 카드를 직접 참조하는 변수도 없다
이제 Customer 인스턴스엔 CreditCard 인스턴스로의 강한 참조가 있고,
CreditCard 인스턴스엔 Customer 인스턴스로의 소유하지 않는 참조가 있다
소유하지 않는 customer 참조로 인하여 john 변수가 쥔 강한 참조를 끊을 때,
Customer 인스턴스로의 강한 참조는 더 이상 존재하지 않는다
Customer 인스턴스로의 강한 참조가 더 이상 없기 때문에 이를 메모리에서 해제한다
이게 발생한 후엔, CreditCard 인스턴스로의 강한 참조도 더 이상 없으므로, 이것도 해제한다
마지막 코드는 john 변수를 nil 로 설정한 후엔
Customer 인스턴스와 CreditCard 인스턴스 deinit이 둘 다 자신의 “정리 (deinitialized)” 메시지를 print 한다는 걸 보여준다
3) 소유하지 않는 옵셔널 참조 (Unowned Optional References)
클래스로의 옵셔널 참조를 소유하지 않는 걸로 표시할 수 있다
ARC 소유권 모델 (ARC ownership model) 관점에선, 동일한 상황에서 소유하지 않는 옵셔널 참조와 약한 참조 둘 다 사용할 수 있다
차이점은 소유하지 않는 옵셔널 참조를 사용할 땐, 항상 유효한 객체를 참조하고 있는지 또는 nil 로 설정한게 확실한지 직접 책임진다는 점이다
학교에서 한 특별한 학과(department)가 제안한 교육 과정(courses) 을 추적하는 예제가 있다
Department는 학과가 제안한 각각의 교육 과정으로의 강한 참조를 유지한다
ARC 소유권 모델에선, 학과가 자신의 교육 과정을 소유한다
Course에는 두 개의 소유하지 않는 참조가 있으며, 하나는 학과로 향하고 다른 하나는 학생이 그 다음 들어야 할 교육 과정으로 향하는데
교육 과정은 이 두 객체 중 어느 것도 소유하지 않는다
모든 교육 과정은 어떠한 학과의 일부라서 department 프로퍼티는 옵셔널이 아니다
하지만, 일부 교육 과정엔 뒤따라서 추천할 교육 과정이 없기 때문에, nextCourse 프로퍼티는 옵셔널이다
아래는 해당 클래스들을 사용한 예제다
위 코드는 한 학과 및 그의 세 교육 과정을 생성한다
입문(intro) 및 중급(intermediate) 둘 다 자신의 nextCourse 프로퍼티에 그 다음으로 제안할 교육 과정을 저장하는데,
이걸 완료한 학생이 들어야 할 교육 과정에 대한 소유하지 않는 옵셔널 참조를 유지한다
소유하지 않는 옵셔널 참조는 자신이 옵셔널로 래핑한 클래스 인스턴스를 강하게 쥐지 않아서, ARC 가 인스턴스를 해제하는 걸 막지 않는다
소유하지 않는 옵셔널 참조는, nil 이 될 수 있다는 것만 제외하면, ARC 밑에서 소유하지 않는 참조와 똑같이 동작한다
옵셔널-아닌 소유하지 않는 참조와 마찬가지로, nextCourse는 항상 해제하지 않은 교과 과정만 참조한다는 보장을 직접 책임져야 한다
이 경우, 예를 들어, department.courses에서 한 교육 과정을 삭제할 땐
다른 교육 과정에 있을지 모를 삭제할 과정으로의 어떤 참조도 제거할 필요가 있다
소유하지 않는 참조와 암시적으로 언래핑하는 옵셔널 프로퍼티 (Unowned References and Implicitly Unwrapped Optional Properties)
위의 약한 및 소유하지 않는 참조 예제에서 다루는 건
강한 순환 참조를 끊어야하는 좀 더 일반적인 시나리오 두 가지였다
Person과 Apartment 예제는,
둘 다 nil 을 허용한, 두 프로퍼티가 강한 순환 참조를 일으킬 수 있는 상황을 보여준다
이런 시나리오의 해결엔 약한 참조가 적합하다
Customer와 CreditCard 예제는,
nil 을 허용한 프로퍼티 하나와 nil 일 수 없는 또 다른 프로퍼티가 강한 순환 참조를 일으킬 수 있는 상황을 보여준다
이런 시나리오의 해결엔 소유하지 않는 참조가 적합하다
하지만, 프로퍼티 둘 다 (both) 값이 항상 있어야 하고,
초기화를 한 번 완료하면 어떤 프로퍼티도 nil 이면 안되는, 세 번째 시나리오가 있다
해당 시나리오에선, 한 클래스의 소유하지 않는 프로퍼티와 다른 클래스의 암시적으로 언래핑하는 옵셔널 속성을 조합하는 게 적합하다
이는 초기화를 한 번 완료하면 옵셔널 언래핑 없이 프로퍼티 둘 다에 직접 접근하면서도, 순환 참조를 피할 수 있게 한다
이번 챕터는 그런 관계를 설정하는 방법을 보여준다
아래 예제는, Country와 City 라는 두 클래스를 정의하는데,
각각 프로퍼티로 서로의 클래스 인스턴스를 저장한다
해당 데이터 모델에서, 모든 국가엔 반드시 항상 수도가 있어야 하고, 모든 도시는 반드시 항상 국가에 소속돼야 한다
이를 나타내기 위해, Country 클래스엔 capitalCity 프로퍼티가 있고, City 클래스엔 country 프로퍼티가 있다
두 클래스 사이에 상호 의존성을 설정하기 위해,
City 이니셜라이저는 Country 인스턴스를 취하고, 이 인스턴스를 자신의 country 프로퍼티에 저장한다
City 이니셜라이저는 Country 초기자 안에서 호출한다
하지만, Two-Phase Initialization에서 설명한 것처럼,
새 Country 인스턴스 완전히 초기화하기 전까진 Country 이니셜라이저가 self 를 City 이니셜라이저에 전달할 수 없다
이 필수 조건에 대처하려면, Country의 capitalCity 프로퍼티를 암시적으로 언래핑하는 옵셔널 속성으로 선언하고자,
자신의 타입 보조 설명 끝에 느낌표를 붙여 City!라고 지시한다
이는, 다른 어떤 옵셔널 같이, capitalCity 속성도 nil 이라는 기본 값을 가지지만,
Implicitly Unwrapped Optionals에서 설명한 것처럼 값을 언래핑하지 않고도 접근 할 수 있다는 의미다
capitalCity에는 기본 값 nil 이 있기 때문에,
Country 인스턴스가 이니셜라이저 안에서 자신의 name 프로퍼티를 설정하자마자
곧 새로운 Country 인스턴스는 완전히 초기화되는 걸로 고려한다
이는 name 프로퍼티를 설정하자마자 곧 Country 이니셜라이저가 암시적 self 프로퍼티의 참조와 전달을 시작할 수 있다는 의미이다
그리하여 Country 이니셜라이저가 자신만의 capitalCity 프로퍼티를 설정할 때
Country 이니셜라이저가 City 이니셜라이저의 매개 변수로 self를 전달할 수 있게된다
모든 게 의미하는 건, 강한 순환 참조의 생성 없이 Country와 City 인스턴스를 단일 구문으로 생성할 수 있으며,
느낌표로 자신의 옵셔널 값을 언래핑 할 필요 없이, 직접 capitalCity 프로퍼티에 접근할 수 있다는 의미다
위 예제에서, 암시적으로 언래핑하는 옵셔널을 사용하는 의미는
2단계 클래스 이니셜라이저의 모든 필수 조건을 만족한다는 거다
capitalCity 프로퍼티는 초기화를 한 번 완료하면 옵셔널-아닌 값처럼 사용하고 접근할 수 있으면서도 강한 순환 참조도 피할 수 있다
클로저의 강한 순환 참조
위에서 두 클래스 인스턴스 프로퍼티가 서로의 강한 참조를 쥘 때
강한 순환 참조가 생성될 수 있는 경우를 봤다
또한, 약한 및 소유하지 않는 참조를 사용하여 강한 순환 참조를 끊는 방법도 봤다
클래스 인스턴스의 프로퍼티에 클로저를 할당하고, 해당 클로저 본문은 인스턴스(self)를 캡쳐 경우에도 강한 순환 참조가 일어날 수 있다
캡쳐는, self.someProperty 같이, 클로저 본문이 인스턴스의 프로퍼티에 접근하거나,
self.someMethod() 같이, 클로저가 인스턴스의 메소드를 호출할 때 일어날 수 있다
어느 경우든, 이러한 접근은 클로저가 self 를 “캡쳐” 하여, 강한 순환 참조를 생성한다
이런 강한 순환 참조는 클로저가, 클래스 같은, 참조 타입 (reference type)이기 때문에 발생한다
프로퍼티에 클로저를 할당할 땐, 해당 클로저로 참조(reference)를 할당하고 있다
본질적으로, 이는 위와 동일한 문제인 두 개의 강한 참조가 서로 살아 있도록 유지된다
하지만, 두 클래스 인스턴스라기 보단, 이번에는 클래스 인스턴스와 클로저의 강한 참조가 서로 살아 있도록 유지된다
Swift는 문제의 풀이법으로, 클로저 캡쳐 리스트를 제공한다
하지만, 클로저 캡쳐 리스트로 강한 순환 참조를 끊는 방법을 배우기 전에,
그런 순환이 일어나는 원인을 이해햐는게 중요하다
아래 예제는 self 를 참조한 클로저를 사용할 때
강한 순환 참조를 생성할 수 있는 경우를 보여준다
예제는 HTMLElement 라는 클래스를 정의하여, HTML 문서 안의 개별 원소를 단순하게 모델링한다
HTMLElement 클래스는 name 프로퍼티를 정의하여,
제목 원소면 "h1", 문단 원소면 "p", 줄 끊음 원소면 "br" 같은, 원소 이름을 지시한다
HTMLElement 는 옵셔널 text 프로퍼티도 정의하는데,
여기에 문자열을 설정하면 그 HTML 원소 안에 그릴 텍스트를 나타낼 수 있다
단순한 두 프로퍼티에 더해, HTMLElement 클래스는 asHTML 이라는 lazy 프로퍼티도 정의한다
해당 프로퍼티는 name 과 text 를 HTML 문자열 조각으로 조합하는 클로저를 참조한다
asHTML 프로퍼티의 타입은 () -> String, 또는 “매개 변수가 없고, String 값을 반환하는 함수” 다
기본적으로, asHTML 프로퍼티에 할당한 클로저는 HTML 태그를 문자열로 나타낸 걸 반환한다
태그는 text 가 존재하면 그 옵셔널 값을, 존재하지 않으면 아무런 텍스트 내용물도 담지 않는다
문단 원소 "p"이면, text 프로퍼티가 "some text" 또는 nil 여부에 따라, 클로저가 "<p>some text</p>" 나 <p /> 를 반환하게 된다
asHTML 프로퍼티의 이름과 사용법은 어느 정도 인스턴스 메소드와 비슷하다
하지만, asHTML은 인스턴스 메소드라기 보단 클로저 프로퍼티이기 때문에,
만약 특정 HTML 요소의 렌더링을 변경하려는 경우, asHTML 프로퍼티의 기본 값을 커스텀 클로저로 교체할 수 있다
예를 들어, 빈 HTML 태그를 반환하지 방지하기 위해
text 프로퍼티가 nil이면 어떠한 텍스트로 기본 설정되는 클로저를 asHTML 프로퍼티에 설정할 수 있다
HTMLElement 클래스가 제공한 단일 이니셜라이저는,
name 매개 변수와 원할 경우 새 원소를 초기화하는 text 매개 변수를 가진다
클래스는 deinit도 정의하는데, 이는 HTMLElement 인스턴스가 해제할 때를 보여주는 메시지를 print 한다
HTMLElement 클래스로 새로운 인스턴스를 생성하고 출력하는 방법은 이렇다
불행하게도, 위에서 작성한, HTMLElement 클래스는 HTMLElement 인스턴스와
자신의 기본 asHTML 값이 사용한 클로저 사이에 강한 순환 참조를 생성한다
인스턴스의 asHTML 프로퍼티은 자신의 클로저로 강한 참조를 쥔다
하지만, 클로저가 self.name 과 self.text를 참조하는 식으로 자신의 본문 안에서 self 를 참조하기 때문에,
클로저가 ‘self’ 를 캡쳐하며, 이는 HTMLElement 인스턴스로의 강한 참조를 되돌려 쥔다는 걸 의미한다
둘 사이에 강한 순환 참조가 생성된다
클로저의 캡쳐링에 대한 더 많은 정보는, Capturing Values 페이지를 보자
paragraph 변수에 nil 을 설정하여 HTMLElement 인스턴스로의 강한 참조를 끊으면,
강한 순환 참조 때문에, HTMLElement 인스턴스나 자신의 클로저 어느 것도 해제되지 않는다
HTMLElement deinit 안의 메시지를 print 하지 않아, HTMLElement 인스턴스가 메모리에서 해제되지 않음을 보여준다
클로저의 강한 순환 참조 해결하기 (Resolving Strong Reference Cycles for Closures)
클로저와 클래스 인스턴스 사이의 강한 순환 참조를 해결하려면
클로저 정의 부분에 캡쳐 리스트를 정의하면 된다
캡쳐 리스트는 클로저 본문 안에서 하나 이상의 참조 타입을 붙잡을 때 사용할 규칙을 정의한다
두 클래스 인스턴스 사이의 강한 순환 참조처럼, 각각의 붙잡을 참조를 강한 참조 보단 약한 또는 소유하지 않는 참조로 선언한다
약한 또는 소유하지 않음을 적절하게 선택하는 건 다른 코드와의 관계에 달려 있다
1) 캡쳐 리스트 정의하기 (Defining a Capture List)
캡쳐 리스트의 각 항목은 weak 또는 unowned 키워드와 self 같은 클래스 인스턴스
또는 delegate = self.delegate 같이 어떠한 값으로 초기화한 변수로의 참조가 쌍을 이룬다
이 쌍들은 한 쌍의 대괄호 안에 쉼표로 구분하여 작성한다
클로저가 매개 변수 목록과 반환 타입을 제공하면 그 앞에 캡쳐 리스트를 작성한다
클로저가 콘텍스트로 추론될 수 있기 때문에 매개 변수 목록이나 반환 타입을 지정하지 않으면,
캡쳐 리스트를 클로저 시작 부분에 두고, 그 뒤에 in 키워드가 따라온다
사실, 두 경우 모두 캡쳐 리스트가 클로저 본문 가장 앞에 있다
그러므로 캡쳐 리스트는 클로저 본문 맨 앞에 둔다라고 생각하면 된다
2) 약한 및 소유하지 않는 참조 (Weak and Unowned References)
클로저와 캡쳐할 인스턴스가 항상 서로를 참조하며, 항상 동시에 해제할 땐 클로저 안의 캡쳐 리스트를 소유하지 않는 참조로 정의한다
거꾸로 말해서, 캡쳐한 참조가 미래의 어떠한 시점에 nil이 될 수도 있을 땐 캡쳐를 약한 참조로 정의한다
약한 참조는 항상 옵셔널 타입이며, 자신이 참조한 인스턴스를 해제할 땐 자동으로 nil이 된다
이는 클로저 본문 안에서 인스턴스 자신이 존재하는지 검사할 수 있게 한다
위의 Strong Reference Cycles for Closures에 있는 HTMLElement 예제의 강한 순환 참조를 해결하는데는
소유하지 않는 참조가 사용하기 적절한 캡쳐 방법이 된다
다음은 순환을 피하는 HTMLElement 클래스의 작성 방법이다
해당 HTMLElement 구현은, asHTML 클로저 안에서 캡쳐 리스트의 추가만 제외하면, 이전 구현과 완전히 똑같다
이 경우, 캡쳐 리스트는 [unowned self] 인데, 이는 “‘self’ 를 강한 참조 보단 소유하지 않는 참조로 붙잡아라” 는 의미다
HTMLElement 인스턴스의 생성과 print는 이전 처럼 할 수 있다
캡처 리스트가 제자리에 있는 참조는 이렇게 보인다
이번에, 클로저가 캡쳐한 self는 소유하지 않는 참조라, 자신이 캡쳐한 HTMLElement 인스턴스를 강하게 쥐지 않는다
paragraph 변수로부터의 강한 참조에 nil 을 설정하면, 아래 예제에서 deinit 메시지를 print 하는 걸로 볼 수 있듯이,
HTMLElement 인스턴스를 메모리에서 해제하게 된다
붙잡을 목록에 대한 더 많은 정보는, Capture Lists (붙잡을 목록) 페이지에서 볼 수 있다
The text was updated successfully, but these errors were encountered: