티스토리 뷰

https://developer.apple.com/videos/play/wwdc2021/10216/?time=1190 

 

ARC in Swift: Basics and beyond - WWDC21 - Videos - Apple Developer

Learn about the basics of object lifetimes and ARC in Swift. Dive deep into what language features make object lifetimes observable,...

developer.apple.com

ARC in Swift: Basics and beyond

Swift의 ARC에 대해서 이야기 하려고 합니다.

오브젝트의 라이프 사이클은 init()으로 시작해서 마지막 사용 후 끝납니다.

라이프타임이 끝나면 ARC는 오브젝트를 해제합니다.

ARC는 오브젝트의 라이프타임을 참조 횟수로 추적합니다.

스위프트 컴파일러는 retain(유지)/release(방출) 동작을 넣습니다.

스위프트는 참조횟수가 0인 오브젝트를 런타임에 해제합니다.

 

여행 앱을 만든다고 생각해봅시다.

traveler1은 Traveler 오브젝트의 첫 번째 레퍼런스입니다.

traveler2 전에 일단 컴파일러가 1에대한 release를 추가합니다.

traveler2에서 참조를 시작하니까 컴파일러가 retain을 추가합니다.

그리고 traveler2가 끝나는 곳에서 release를 추가합니다.

traveler1에 의해 Traveler ref count 1증가

traveler2에 의해 Traveler ref count 1증가 (카운트는 2)

relase에 의해 count 감소

 

relase에 의해 count 감소

오브젝트의 라이프타임은 그림과 같습니다.

스위프트에서 오브젝트의 라이프타임은 사용 베이스입니다.

오브젝트가 최소 라이프타임을 보장한다면 마지막 사용에서 끝납니다.

 

이것이 C++같은 다른 언어랑 다른데, 이런 언어들은 오브젝트 라이프타임이 닫는 괄호에서 끝납니다.

라이프타임이 끝나면 오브젝트는 HERE에서 해제됩니다.

근데 실제로 컴파일러의 최소 시간과 관련이 되어있어서 오브젝트 라이프타임이 마지막에 끝날 수 있습니다.

이게 대부분 케이스에서는 중요하지 않지만 weak 나 unowned 참조에서는사이드 이펙트가 있을 수 있습니다.

 

순환참조 내용이 나오는데 간략하게만 소개하겠습니다.

 

네... 이렇게 서로 참조하는 경우 ref count 가 0이 되지 않아서 순환참조가 발생합니다.

 

weak를 사용하면 ref count가 증가하지 않습니다.

그래서 Traveler 의 마지막 사용에 카운트가 0이됩니다.

Traveler 카운트가 0이되면 해제될 수 있습니다.

 

이 예시는 단순히 순환 참조를 제거하기 위해 weak를 사용한 경우입니다.

 

만약에 라이프 타임이 끝나고 weak 참조에 접근하면 문제가 생길 수 있습니다.

이렇게 바꿔보겠습니다.

여기서 account.printSummary()를 하면 어떻게 될까요.

예상한대로 Lily has 1000 points가 출력됩니다.

하지만 이것은 우연이라고 합니다.

(내용을 추가하자면, xcode에서 저 코드를 실행해도 crash는 발생하지 않습니다. crash가 발생하는 case는 마지막에 알려드리겠습니다.)

 

만약에 컴파일러가 Traveler의 사용 직후에 바로 release해버린다면 Traveler의 참조 횟수는 0이될겁니다.

그러면 weak traveler는 nil이 될 것이고, crash가 발생합니다.

 

옵셔널 바인딩을 하면서 해결할 수 있다고 생각하지만 이건 더 나쁜 문제입니다.

명백한 Crash가 없으니까, 오브젝트의 라이프타임 체크가 안되니까 버그가 발생하는걸 모를 수 있기 떄문입니다.

이러한 문제를 해결하는 방법이 있습니다.

연장된 라이프 타임을 이용하는 것입니다.

이걸 마지막에 empty call 로 처리해도 됩니다.

defer를 사용하면 더 복잡한 상황에서도 쓸 수 있습니다.

다른 방법은 강한 참조에 접근하도록 설계를 바꾸는 것입니다.

weak에 접근하지 못하도록하면 됩니다.

 

weak 와 unowned 참조는 왜 필요할까요?

오직 순환 참조를 없애기 위해서?

순환 참조는 트리구조로 바꿈으로써 해결 할 수 있습니다.

Traveler 클래스는 Account 클래스를 참조해야합니다.

AccountTraveler를 참조할 필요가 없습니다. 단지 개인정보에만 접근하면 됩니다.

PersonalInfo라는 새로운 클래스를 만들어서 정보를 옮기겠습니다. 

둘다 PersonalInfoPersonalInfo 를 참조하기 때문에 순환을 막을 수 있습니다.

 

추가적인 비용이 들어가지만모든 오브젝트 라이프타임 버그를 제거할 수 있는 확실한 방법입니다.

deinit 또한 사이드 이펙트를 만들 수 있습니다.

 

 

라이프타임 끝나는게 ARC 설정에 따라 다르기 때문에

print("Done traveling")에서 끝날수도 그 위destination = "Big Sur"에서 끝날수도 있습니다.

즉 deinit의 print가 명확히 어딘지 알수 없죠.

대략 destination을 받아서 가장 인기있는 카테고리를 계산하고 id, count, category를 publish하는 복잡한 구조의 예시입니다.

생성되고

카피되고

Big sur 업데이트, 기록

Catalina로 업데이트, 기록

deinit이 실행되고 category로 Nature를 pulish합니다.

하지만 traveler를 사용하고 바로 deinit된다면,

travel interest를 계산하기전에 Traveler 오브젝트가 deinit되기 떄문에 nil이 publish됩니다.(버그)

weak 때처럼 해결하는 방법을 봅시다.

withExtendedLifeTime을 사용하는 방법

 

deinit 사이드이펙트는 effects가 전부 local이면 발생하지 않습니다

private으로 접근 제한

deinit에서 compute를하고 pulish를 합니다.

 

defer을 사용해서 publish를 할 수 있습니다. deinit은 검증에만 사용됩니다.

deinit 사이드이펙트를 제거함으로써, 모든 오브젝트 라이프타임 버그를 제거할 수 있습니다.

 

 

Xcode13 Optimize Object Lifetiems

ARC optimizations으로 라이프타임을 짧게 할 수 있습니다.

yes로 하면 minimum으로 되고 아까 위에서 말한 현상을 볼 수 있습니다.

 

타겟에서 다음을 yes로 바꾸고 아래 코드로 테스트 해보세요.

 

class Traveler {
    var name: String
    var account: Account?
    
    init(name: String) {
        self.name = name
    }
}

class Account {
    weak var traveler: Traveler?
    var points: Int
    func printSummary() {
        print("\(traveler!.name) has \(points) points")
    }
    init(traveler: Traveler, points: Int) {
        self.traveler = traveler
        self.points = points
    }
    deinit {
        print("\(self) deinit")
    }
}

func test() {
    let traveler = Traveler(name: "Lily")
    let account = Account(traveler: traveler, points: 1000)
    traveler.account = account
    
    account.printSummary()
}
print("test")
test()
댓글
공지사항