- 객체의 lifetime은 초기화를 통해 시작하고 마직막 사용 후 끝난다.
- ARC는 lifetime이 끝나면 객체의 할당을 해제한다.
- ARC는 객체의 lifetime을 reference count를 사용해 추적한다.
- Swift
컴파일러
는 retain / release 연산자를 삽입한다. - Swift는
runtime
에 reference count가 0 이면 할당을 해제한다.
- 참조가 시작될 때 retain 삽입
- 참조의 마지막 사용후 release 삽입
- traveler1은 Traveler 객체에 첫번째 참조이다.
- traveler1의 마지막 사용은 복사이다(traveler2에 복사)
- traveler1의 마지막 사용 직후 Swift Compiler가 release를 삽입
- retain을 삽입하지 않았는데, initialization이 reference count를 1로 설정해준다.
- traveler2는 Traveler 객체의 또 다른 참조이다.
- traveler2의 마지막 사용은 destination 업데이트 작업이다.
- Swift Compiler는 참조가 시작될 때 retain 연산자를 삽입합니다.
- 참조의 마지막 사용 직후 release 연산자를 삽입합니다.
- Traveler 객체가 heap에 생성되고 reference count 1로 설정
- Traveler 객체의 추가 참조로 reference count 2로 증가
- Traveler 1의 마지막 사용 후 reference count 1로 감소
- traveler2의 destination 업데이트하는 마지막 사용 후 reference count 0으로 감소
- referece count가 0 이므로 할당 해제할 수 있다.
- Swift에서 객체의 lifetime은 사용 기반이다(use-based)
- 객체의 초소 lifetime이 보장된다(초기화 후 시작 - 마지막 사용 후 끝)
- C++이랑 다름
- C++의 lifecycle은 '}' 에 도달했을 때 까지 보장됨
- 실제로는 최소 관찰되는 lifetime은 최소 보장과 다르게 마지막 사용 이후에 종료된다.(HERE에서 해제됨)
- retain, release 연산자로 관리돼서 차이날 수 있음
- ARC 최적화에 따라 달라짐
- 대부분의 경우 에서는 문제 안됨
- Weak & unowned 참조, Deinitializer의 size-effects 를 활용하면 해결 가능
- guranteed object lifetime 대신에 observed object lifetime에 의존하면 나중에 버그 생김
- weak, unowned 참조를 사용하면 reference counting에 참여하지 않는다. 그래서 reference cycle을 해제하는데 많이 사용한다.
- Account는 Traveler를 참조하고 Traveler는 Account를 참조한다.
-
Traveler 객체 생성
-
Traveler reference count: 1
-
Account 객체 생성
-
account가 traveler 참조
-
Traveler reference count: 2
-
Account reference count: 1
-
traveler가 account 참조
-
Traveler reference count: 2
-
Account reference count: 2
-
account의 마지막 사용
-
Traveler reference count: 2
-
Account reference count: 1
-
traveler의 마지막 사용
-
Traveler reference count: 1
-
Account reference count: 1
- 순환 참조때문에 reference count 1씩 남음
- 메모리에서 해제 안됨
- 메모리 누수 발생
- weak or unowned를 사용하면 참조중인 객체가 사용중에 메모리에서 해제될 수도 있습니다.
-
Account의 traveler를 weak로 선언해 순환 참조 방지
-
guaranted object lifetime이후에 약한 참조에 접근하거나 observed object lifetime에 의존하면 나중에 버그 발생
- Account로 printSummary 메소드 이동
- traveler의 name 과 points의 print는 우연이다(실패할 수있다.)
- 메모리에서 해제된 traveler를 참조할 수 있다.
- 강제 언랩핑대신 옵셔널 바인딩 사용
- 옵셔널 바인딩은 문제를 더 악화시킨다.
- crash 대신 silent bug 생성
- weak, unowned reference를 안전하게 다루는 방법
- 각각은 초기 구축 비용과 지속적인 유지 비용의 정도가 다릅니다.
- withExtendedLifetime()을 사용하면 안전하게 객체의 lifetime을 연장할 수 있습니다.
- scope의 마지막에 빈 withExtendedLifetime()을 놓아도됨
-
defer을 사용해도 됨
-
쉽게 lifetime 버그 잡는거같지만 이 기술을 fragile 하고 정확성의 책임을 너에게 전달한다
-
이러한 저근방식으로는 약한 참조가 버그를 발생시킬 가능성이 있는데 마다 withExtendedLifetime()을 사용해야된다.
-
withExtendedLifetime이 제어되지 않으면 유지 보수 비용을 증가시킬 수 있다.
- printSummary()를 Traveler로 올겼고 printSummary()는 이제 강한참조를 통해서만 호출됩니다.
- weak, unowned 참조는 성능 부담외에도 클래스 설계를 주의하지 않으면 문제를 일으킬 수 있다.
- weak, unowned는 순환 참조를 피할 때만 사용해야되나??
- 처음 부터 피하게 설계한다면??
- Account class는 traveler의 personal information에만 접근하면됨.
- traveler의 개인정보를 PersonalInfo라는 새로운 class로 이동
- 추가 구현 비용이 들지만 잠재적인 객체 수명 버그를 제거하는 명확한 방법이다.
- 객체의 수명을 관찰할 수 있는 다른 방법은 deinitializer의 side-effect이다.
- 할당이 해제되지 전에 실행되고 side effect로 외부 프로그램에 영향을 줄 수 있다.
- 외부 프로그램에 영향을 주는 연속 deinitializer를 작성한다면 버그로 이어질 수 있다.
- deinitializer는 print하는 글로벌 사이드 이펙트 있다.
- 오늘은 "Done traveling" 이후에 deinitializer 실행되지만 다음에는 전에 deinitializer가 실행될 수 있다.
- ARC의 최적화에 따라 달라짐
- Traveler class에 TravelMetrics class를 도입함.
- Traveler 객체가 해제될 때, metric이 publish() 실행
- computTravelInterest()와 verifyGlobalTravelMetric()의 순서가 상황에 따라 바뀐다.
- 할당 해제후 computTravelInterest() 실행되면 nil 값으로 버그 발생
- deinitializer의 사이드이펙트 안전하게 해결하는 방법
- withExtendedLifetime() 사용
- internal -> private로 변경
- deinitializer 대신 defer 사용해 publish함
- deinitializer는 검증만함
- deinitializer의 사이드 이펙트를 제거하면 모든 잠재적 객체 수명 버그를 제거할 수 있다.
- Xcode13 에서 "Optimize Object Lifetimes"이용 가능하다
- ARC 최적화로 수명을 단축시킨다.