ARC in Swift: Basics and beyond - WWDC21
Swift에서는 가능하다면 Value Type 사용하는게 더 좋은데 Reference Type을 사용해서 의도치 않은 데이터 공유에 따른 오류를 피할 수 있음.
그럼에도 불구하고 class와 같은 Reference Type을 사용해야 한다면 Swif에서 ARC를 통해 메모리를 관리하고 효과적으로 코드를 작성하기 위해서는 ARC의 동작 방식을 이해하는게 도움이 됨.

Object lifetimes and ARC
: Swift에서 객체 수명과 ARC에 대한 리뷰
Observable object lifetimes
: 관찰 가능한 객체의 수명이 무엇인지 설명
: 관찰 가능하게 만드는 언어적 특징과 관촬된 객체 수명에 의조낳는 것의 결과와 이를 수정하는 안전한 기술에 대해서 알아볼 예정
Object lifetimes and ARC

Swift에서 객체의 수명은 초기화 시점부터 시작해 마지막 사용 시점까지.
: ARC는 객체의 수명이 끝나면 해당 객체의 할당을 해제하여 자동으로 메모리를 관리
: Swift의 ARC는 주로 retain 및 release 작업을 Swift 컴파일러에 의해 구동
: ARC가 0으로 되면 dealloc 시킴

traveler1의 참조의 시작과 끝을 나타냄
: Traveler 객체 생성에서 시작
: traveler2의 복사본에서 끝

Swift 컴파일러는 traveler1 참조의 마지막 사용 직후에 relase 작업을 삽입함.
여기서 retain은 init 시점에 추가됨.

traveler2의 참조의 시작과 끝을 나타냄.
: Traveler 객체에 대한 또 다른 참조로 시작
: traveler2 destination 값을 업데이트에서 끝

Swift 컴파일러는 참조가 시작될 때 retain을 삽입하고 참조를 마지막으로 사용한 직후에 release를 삽입함
Traveler 객체가 런타임에 힙에 생성되고 아래와 같은 순서로 메모리 할당 및 해제가 진행됨
func test() {
let traveler1 = Traveler(name: "Lily")
// traveler2 retain
let traveler2 = traveler1
// traveler1 release
traveler2.destination = "Big Sur"
// traveler2 release
}



관찰된 객체의 보장된 최소 수명은 객체가 끝날때이지만 ARC 최적화에 따라서는 객체가 마지막 사용 시점을 지나 더 오래 살아 남아있을 수도 있으며, 이런 경우에는 해당 객체는 프로그램의 마지막 지점에서 할당이 해제됨.
Swfit는 C++과 같은 언어처럼 중괄호에서 끝나도록 보장되는 언어와 다를 수 있음.
** ARC 최적화 **
: Xcode 13에서 Optimize Object Lifetime가 등장했으나 Xcode 14에서는 해당 옵션이 최적화로 항상 적용되고 사용자에 의해 적용된 옵션은 무시되었으며, Xcode 26 기준으로는 사용할 수 없음.

대부분의 경우에는 객체의 생명주기가 중요하지 않음
: weak, unowned, deinit side-effects와 같은 언어적 특성을 활용하면 객체의 생명주기를 관찰하는 것이 가능함.
: 하지만 이는 프로그램의 잠재점 문제점이 될 수 있음.
보장된 객체 수명 대신 관찰된 객체 수명에 의존하는게 현재 당장은 문제가 발생할 수 있는데, 이는 단순히 우연에 일치
: ARC최적화나 컴파일러 업데이트 등에 의해서 어느날 갑자기 문제가 발생할 수 있음.
Observable object lifetimes
기본적인 참조와 달리 weak과 unowned는 reference count를 증감에 관여하지 않음.
: reference cycle을 끊는데 사용

이렇게 코드를 작성할 경우 reference cycle로 인해 Swift에서는 메모리 Leak이 발생.

Swift Runtime은 약한 참조에 대한 접근을 nil로 안전화게 전환하고 소유하지 않은 참조에 대한 접근을 trap으로 전환함
약한 참조는 소유되지 않음으로 표시되어 reference cycle을 끊을 수 있음.
** trap **
: Swift에서 trap은 런타임 검사를 통과하지 못하면 즉시 중단시키는 안전장치로 dangling pointer 접근을 방지하기 위함

약한 참조를 사용하면 reference count가 증가하지 않아서 Traveler 객체의 ref_count가 0이 되면 해당 객체의 할당이 해제될 수 있음.

printSummary() 함수 호출 시 traveler의 name과 point가 출력될 수는 있지만 이것도 단지 우연의 일치.
: 왜냐하면 Traveler 객체를 마지막으로 사용한 곳이 traveler.account = account이기 때문에임.
만약 컴파일러가 마지막 사용 이후 release를 삽입한다면 Traveler의 ref_count는 0으로 떨어지고, Account에 위치한 Traveler는 결국 nil이 되어서 문제가 발생할 수 있음.
즉, 우연의 일치라는 것임.
func test() {
let traveler = Traveler(name: "Lily")
let account = Account(traveler: traveler, points: 1000)
traveler.account = account
// traveler release
account.printSummary()
}


위의 흐름으로 진행할 경우 크래시가 발생할 수 있음.

optional binding을 통해 크래시를 예방할 수도 있으나 이는 실제 문제를 더 악화시킴.
왜냐하면 크래시가 발생하지 않음으로 인하여 관찰된 객체 수명이 변경될 때 눈에 띄지 않는 버그가 발생하기 때문

이런 weak와 unowned를 안전하게 처리하는 데에는 여러가지 기술이 있으며, 각 기술마다 초기 구현 비용 및 유지 관리 비용이 다름.

withExtendedLifetime을 사용하면 객체의 수명을 연장하여 printSummary() 함수가 호출되는 동안 Traveler 객체의 수명을 안전하게 연장하여 잠재적인 버그를 방지할 수 있음.


끝에 배치하거나 혹은 defer를 통해서도 scope의 끝까지 연장하도록 작성할 수 있음.
하지만, withExtendedLifetime를 사용하는 것은 객체 수명에 대한 문제를 해결하는 쉬운 방법으로 보이지만 이는 유지보수 비용을 증가 시킴.
왜냐하면 약한 참조로 인해 버그가 발생할 가능성이 있을 때마다 withExtendedLiftime을 사용해야 하기 때문

강한 참조를 통해 접근하도록 제한하면 객체 수명에 따른 잠재적인 버그를 막을 수 있음.
하지만, weak이랑 unowned를 사용하기 전에 참처음부터 reference cycle을 만들지 않는 방법도 있음.

위처럼 코드를 설계를 변경하여 대응하면 조금 더 복잡해지긴 하지만 객체 수명과 관련한 버그를 없애는 더 나은 대안임.

deinit은 할당 해제 전에 실행되며, 여기서 발생하는 side effect는 외부에서 관찰될 수 있음.

객체의 라이프 사이클에 따라서 Done traveling이 출력되는 순서가 달라지는데
// console
Done traveling
Lily is deinitailizing

// console
Lily is deinitailizing
Done traveling
로 순서가 달라짐

조금 더 복잡한 예제로 위와 같은 코드가 있다고 가정함.

객체의 수명이 유지되어서 metrics.computeTravelInterest() 이 실행된 경우에는 category가 Nature로 나타남.


위의 경우에는 metrics.computeTravelInterest()가 실행되기 전에 deinit이 되어서 category가 nil로 나타남
객체 수명과 관련한 문제에서 deinit 문제를 근본적으로 해결하기 위해서 deinit side effect를 없애는 것임.

deinit 대신에 defer를 사용하고 deinit에서는 assert를 통해 검증만 수행함.

Xcode 13에서 부터는 Swift 컴파일러에 대해서 최적화하는 옵션을 명시적으로 지정할 수 있음
** Xcode 14 Release Note 참고 **

Swift 컴파일러가 항상 “객체 생명주기 최적화(Object Lifetime Optimization)”를 일관되게 적용하도록 변경
최적화가 항상 켜져있고, 사용자가 끄거나 바꾸는 옵션은 없어짐.
(링크)
https://developer.apple.com/documentation/xcode-release-notes/xcode-14-release-notes