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
미소유 참조는 약한 참조와 다르게 참조 대상이 되는 인스턴스가 현재 참조 하고 있는 것과 같은 생애 주기를 갖거나 더 길다고 본다.
이게 대체 무슨 외계어야 ?! 🥲 🥲
weak 예제와 비교해보면 쉽게 이해할 수 있음
즉 여기예제에서 대입해서 보면, owner(참조하고 있는 프로퍼티)는 참조하고있는 대상(User)와 같은 생애 주기 또는 더 짦아야 한다는 것
차 근 차 근 읽어보면 이해가 갈 수 있음
왜냐면 이 글을 쓰는 '현재의 나'는 이해했기 때문. 미래의 나야 이해했니..?
그런데 owner보다 생애주기가 적어도 더 길어야 하는 User가 레퍼런스 카운트가 0 이 되면서 먼저 메모리에서 해제되었기 때문에
owner는 없는 주소값을 가지고 있기 때문에 참조 할때 에러가 뜨는것이다.
이것으로 내릴수 있는 결론.
unowned로 선언된 참조 프로터티는 살아있는 동안 절대❗️❗️ nil을 할당 하지 않는다.
즉, unowned로 선언된 owner은 옵셔널타입으로 선언하는 것은 잘못된 것임.
우와우... 대충읽으면 절대로 이해할 수 없는 개념이도다....
미소유 참조와 암시적 옵셔널 프로퍼티 언래핑
클래스 인스턴스에서 약한 참조, 미소유 참조는 해당 참조가 nil이 될 수 있느냐 없느냐로 구분할 수 있다.
하지만 이 경우를 제외한 제 3의 경우도 발생할 수 있다.
두 프로퍼티가 절대 nil이 되지 않는 경우임
예제를 보자
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
}}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"
모든 나라는 반드시 수도가 있어야 하고, 모든 수도는 반드시 나라에 포함되어야 한다.
즉, 둘다 nil일 가능성 없음
하지만 서로를 참조해야 하는 상황때문에 강한 참조 순환의 위험성이 생긴다.
이러한 경우 미소유 프로퍼티를 암시적 옵셔널 프로퍼티 언래핑으로 참조 문제 해결
클래스 간의 상호의존을 위해 Country의 init 안에서 capitalCity(City 인스턴스)를 생성한다
이 구조에서는 항상 Country를 먼저 만들어야 City를 만들 수 있다
이니셜라이저 챕터에서 설명한 것처럼 capitalCity의 이니셜라이저에 넘길 self는,
자기자신의 1단계 초기화가 끝날때까지(속성에 값이 다 들어갈 때까지) 유효하지 않다
따라서 capitalCity를 암시적 언랩핑 옵셔널(!)로 설정하여
(1) 초기화 1단계 시점에서 capitalCity가 nil이어도 괜찮도록 하고
(2) 추후 capitalCity에 접근할 때 언랩핑할 필요가 없게 만들고
(3) 결과적으로 클래스 간의 상호의존성을 설정하면서
(4) strong 참조 사이클도 방지할 수 있다
머리 터지기 전에 정리
클래스 인스턴스간의 강한 참조 순환 문제의 해결 정리 ⭐️
[Case1] 서로 nil이 될 수 있을 때 -> weak 참조 이용
[Case2] 한 쪽만 nil이 될 수 있을 때 -> unowned 참조 이용 (사라질수있는 클래스에서 상대방을 unowned 로 참조)
[Case3] 그럼 둘 다 nil이 될 수 없는 경우는 -> unowned 이용하되, 암시적 언래핑 옵셔널 프로퍼티
클로저에서의 강한 참조 순환
클로저에서 강한 참조 순환은 캡쳐랑 관련 있음.
아니 캡쳐가 뭔데 ?! 너무 불친절 하자나!!
변수를 클로저 또는 중첩 함수에서 사용하게 될 경우 , 외부 변수를 내부적으로 저장해서 사용하게 되는데 이를 캡쳐라 부름
❗️ 중요❗️ 클로저는 값을 캡쳐할 때, 클로저 자체가 클래스와 마찬가지로 참조타입이기 때문에
캡쳐할 값이 값타입 이등 참조타입이든 관계없이 항상 참조캡쳐를 한다.
Reference Capture이기 때문에 변수는 참조 관계가 되고, 참조이기 때문에 클로저 내부의 값, 외부의값 변경하면 서로서로 영향을 받는다.
여기까지 이해 했나욘?
강한 참조 순환은 변수 뿐만 아니라 클로저와의 관계에서 발생할 수도 있다. 왜냐하면 클로저에서는 self를 캡쳐하기 때문이다.
이 문제를 해결하기 위해 클로저 캡쳐 리스트를 이용한다.
예제 : 캡쳐 리스트는 무엇인가.. 부글 부글...🔥
참조 관계를 벗어나기 위한 값 타입으로써 이용하고자 할 때 캡쳐리스트를 사용한다.
캡쳐리스트로 캡쳐된 대상은 값타입 뿐만아니라 상수 let으로만 이용할 수 있다.
func firstClosure(){varnumber=20print("1: \(number)")
// number를 내부적으로 저장(캡쳐)하고 있음 = 복사하고있음.
// 구조체, 값, 복사 -> 클래스처럼 참조가 되는 형태로 캡쳐가 되고 있다.
// 왜? -> 클로저: 클로저는 캡처를 무조건 참조타입으로 하게된다. => 클로저는 Reference Capture를 한다.
// [number] 이런식으로 사용할 변수를 괄호안에 주면 해당 변수는 값타입으로 복사 가능 , 상수로 캡처가 됨.
// 그래서 number는 let형태가 되므로 클로저 안에서 변경 불가능하다.
letclosure:()->Void={[number]in
//number = 50 // 오류 발생 💥
print("closure: \(number)")}
number =100print("2: \(number)")closure()}firstClosure()
/*************** 출력 결과****************
1: 20
2: 100
closure: 20
*****************************************/
왜 클로저 안의 number 값은 바뀐 100이 아닌 20이 나왔을까?
값타입으로 이용하려고 캡쳐리스트를 이용하여 캡쳐하였기 때문.
즉, number는 캡쳐리스트의 캡쳐로 변경할 수 없는 let의 값타입으로 이용가능함.
클로저에서의 강한 참조 순환 예제
classHTML{letname:Stringlettext:String?
lazy varasHTML:()->String={iflet 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("HTML Deinit")}}varparagraph:HTML?=HTML(name:"p", text:"hello, world")print(paragraph!.asHTML())
paragraph =nil
/*************** 출력 결과****************
<p>hello, world</p>
*****************************************/
self는 자기 자신 클래스를 가리키는건 상식이니 알쥬?
클로저에서는 self를 참조 타입으로 캡쳐하기 때문에 참조타입 간의 강한 참조 순환이 이루어 진다.
출력 결과에서 알수 있듯이 HTML 클래스가 Deinit되지 않고 있음.
클로저에서의 강한 참조 순환 문제의 해결
classHTML{letname:Stringlettext:String?
lazy varasHTML:()->String={[unowned self]iniflet 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("HTML Deinit")}}varparagraph:HTML?=HTML(name:"p", text:"hello, world")print(paragraph!.asHTML())
paragraph =nil
/*************** 출력 결과****************
<p>hello, world</p>
HTML Deinit
*****************************************/
이러한 경우 캡쳐리스트를 이용하여 self를 값타입으로 복사하되
메모리 참조 방법을 weak, unowned 중에 하나로 명시해주면된다.
asHTML 클로져를 weak, unowned로는 안되나용?
메모리 참조 방법(strong, weak, unowned)는 클래스에서만 쓰일 수 있다.
캡쳐리스트 약한 참조와 미소유 참조 구별법
unowned 참조
클로저와 클로저가 캡쳐할 인스턴스(self) nil이 될 일이 없을 때 -> unowned 참조를 사용.
즉, 항상 동시에 해제된다. (두 개의 라이프사이클이 동일한 경우이다)
weak 참조
클로저가 캡쳐할 인스턴스가 어느 미래 시점에 nil이 될 수 있을 때
optional 타입일 때, 인스턴스가 해제되면 nil이 할당된다.
따라서 클로저 바디 안에서 nil 체크를 하여 해제된 인스턴스에는 접근하지 않을 수 있다.
아래의 글을 막힘없이 이해할 수 있다면 당신은 이미 ARC 고수..
기본적으로 ARC에 대해 알고있어야 이해 가능함
강한 순환 참조
클래스 인스턴스 간의 강한 참조 순환
클로저에서의 강한 참조 순환
클래스 인스턴스간 강한 참조 순환
예제 1 : 참조 순환이 일어나지 않고 자동 Deinit되는 경우
예제 2: 강한 참조 순환이 일어나는 경우
클래스 인스턴스간의 강한 참조 순환 문제의 해결
약한 참조 Weak로 해결
sesac?.owner
에서 에러 발생하지 않음User
가 메모리에서 해제된 뒤sesac?.owner
가 혹시라도 사용되더라도 문제 없다.미소유 참조 unowned로 해결
참조 대상이 되는 인스턴스
가현재 참조 하고 있는 것
과 같은 생애 주기를 갖거나 더 길다고 본다.owner(참조하고 있는 프로퍼티)
는참조하고있는 대상(User)
와 같은 생애 주기 또는 더 짦아야 한다는 것미소유 참조와 암시적 옵셔널 프로퍼티 언래핑
Country
의init
안에서capitalCity
(City 인스턴스)를 생성한다Country
를 먼저 만들어야City
를 만들 수 있다capitalCity
의 이니셜라이저에 넘길self
는,capitalCity
를 암시적 언랩핑 옵셔널(!)로 설정하여capitalCity
가nil
이어도 괜찮도록 하고capitalCity
에 접근할 때 언랩핑할 필요가 없게 만들고머리 터지기 전에 정리
클래스 인스턴스간의 강한 참조 순환 문제의 해결 정리 ⭐️
weak
참조 이용unowned
참조 이용 (사라질수있는 클래스에서 상대방을 unowned 로 참조)unowned
이용하되, 암시적 언래핑 옵셔널 프로퍼티클로저에서의 강한 참조 순환
참조캡쳐
를 한다.캡쳐 리스트
를 이용한다.예제 : 캡쳐 리스트는 무엇인가.. 부글 부글...🔥
클로저에서의 강한 참조 순환 예제
HTML
클래스가Deinit
되지 않고 있음.클로저에서의 강한 참조 순환 문제의 해결
캡쳐리스트 약한 참조와 미소유 참조 구별법
클로저에서의 강한 참조 순환 약한 참조 vs 미소유 참조 정리 ⭐️
그래도 언제 써야 할지 모르겠어? 이 표를 활용해봐
참고자료
The text was updated successfully, but these errors were encountered: