본문 바로가기

All Categories/iOS & Swift

Swift) ARC (Automatic Reference Counting)

반응형

안녕하세요. 재희입니다!

오늘은 Swift의 ARC에 대해 알아보도록 하겠습니다!

https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html

 

Automatic Reference Counting — The Swift Programming Language (Swift 5.6)

Automatic Reference Counting Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. In most cases, this means that memory management “just works” in Swift, and you don’t need to think about memory management your

docs.swift.org


Automatic Reference Counting

SwiftARC(Automatic Reference Counting)를 사용하여 앱의 메모리 사용량을 추적하고 관리합니다.

대부분의 경우 ARC는 메모리 관리가 Swift 내에서 작동함을 의미하며, 메모리 관리에 대해 직접 생각할 필요가 없습니다.

ARC는 클래스 인스턴스가 더 이상 필요하지 않게 되면, 클래스 인스턴스에서 사용되는 메모리를 자동으로 해제합니다.

 

Swift에서 ARC를 사용하는 방법은 Objective-C에서 ARC를 사용하는 방법(Transitioning to ARC Release Notes)과 매우 유사합니다.

 

참조 카운트(Reference counting)클래스의 인스턴스에만 적용됩니다.

구조체 및 열거형은 참조(reference) 유형이 아닌 값(value) 유형이며, 저장되거나 참조로 전달되지 않습니다.

 

ARC 구조

클래스의 새 인스턴스를 만들 때 마다 ARC는 메모리를 할당하여 해당 인스턴스에 대한 정보를 저장합니다.

이 메모리에는 인스턴스 유형에 대한 정보와 해당 인스턴스와 관련되어 저장된 property 값이 저장됩니다.

 

또한 인스턴스가 더 이상 필요하지 않게 되면, ARC는 해당 인스턴스에서 사용되는 메모리를 해제하여 메모리를 다른 용도로 사용할 수 있도록 합니다.

이렇게 하면 클래스 인스턴스가 더 이상 필요하지 않을 때 메모리 공간을 차지하지 않습니다.

 

그러나 ARC가 아직 사용 중인 인스턴스의 할당을 해제하면 해당 인스턴스 속성에 액세스하거나 해당 인스턴스의 메소드를 호출할 수 없게 됩니다.

실제로 인스턴스에 액세스하려고 하면 앱이 크래쉬할 가능성이 높습니다.

 

인스턴스가 필요한 동안 인스턴스가 사라지지 않도록 ARC는 현재 각 클래스 인스턴스를 참조하고 있는 property, constant, variable의 수를 추적합니다.

ARC는 인스턴스에 대한 적어도 1개의 활성 참조가 존재하는 인스턴스의 할당을 해제하지 않습니다.

 

이를 위해 클래스 인스턴스를 속성, 상수 또는 변수에 할당할 때 마다 해당 속성, 상수 또는 변수가 인스턴스를 강력하게(strong) 참조합니다.

참조는 해당 인스턴스를 확실하게 유지하며, 강력한 참조가 남아 있는 한 할당 해제를 허용하지 않으므로 "강한(strong)" 참조라고 합니다.

 

클래스 인스턴스 간의 강한 참조 주기 해결

weak 참조unowned 참조를 사용하면 참조 사이클 내의 한 인스턴스강력한 유지 없이 다른 인스턴스를 참조할 수 있습니다.

그러면 인스턴스는 강한 참조 사이클을 생성하지 않고 서로 참조할 수 있습니다.

 

다른 인스턴스의 라이프타임이 짧은 경우, 즉 다른 인스턴스의 할당을 먼저 해제할 수 있는 경우 weak 참조를 사용합니다.

예시로 아파트의 라이프타임 중 세입자가 없는 시점이 있을 수 있기 때문에, 이 경우 약한(weak) 참조가 참조 사이클을 깨는 적절한 방법입니다.

반면 다른 인스턴스의 라이프타임이 같거나 긴 경우에는 unowned(소유되지 않은) 참조를 사용하는 것이 좋습니다.

 

Weak References

약한 참조는 참조가 참조하는 인스턴스를 강하게 유지하지 않으므로 ARC가 참조된 인스턴스를 폐기하는 것을 막지 않습니다.

이 동작으로 인해 참조가 강력한 참조 사이클의 일부가 되는 것을 방지합니다.

속성 또는 변수 선언 앞에 weak 키워드를 배치하며 약한 참조를 나타냅니다.

 

약한 참조는 참조하는 인스턴스를 강하게 유지하지 않기 때문에 약한 참조가 참조하는 동안 해당 인스턴스가 할당 해제될 수 있습니다.

따라서 ARC는 참조하는 인스턴스가 할당 해제되면 자동으로 약한 참조를 nil으로 설정합니다.

값을 nil으로 변경할 필요가 있기 때문에 항상 상수가 아닌 optional 유형의 변수로 선언됩니다.

 

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) is being deinitialized") }
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    weak var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

두 인스턴스를 함께 연결하면 다음과 같은 참조가 나타납니다:

Person 인스턴스는 아파트 인스턴스에 대한 강한 참조를 가지고 있지만, Apartment 인스턴스는 현재 사용자 인스턴스에 대한 약한 참조를 가지고 있습니다.

즉 john 변수를 nil으로 설정하여 강한 참조를 끊으면 사용자 인스턴스에 대한 강한 참조가 더 이상 존재하지 않습니다.

john = nil
// Prints "John Appleseed is being deinitialized"

 

Person 인스턴스에 대한 강한 참조가 더이상 없으므로 해당 인스턴스는 할당 해제 되고 tenent 속성은 nil로 설정됩니다:

 

Unowned references

약한 참조와 마찬가지로 미소유 참조는 참조하는 인스턴스를 강하게 유지하지 않습니다.

그러나 약한 참조와 달리 소유되지 않은 참조는 다른 인스턴스의 수명이 같거나 긴 경우에 사용됩니다.

속성 또는 변수 선언 앞에 unowned 키워드를 배치하여 미소유 참조를 나타냅니다.

 

약한 참조와 달리 미소유 참조는 항상 값이 있을 것으로 예상됩니다.

따라서 값을 소유하지 않음으로 표시한다고 해서 optional이 되는 것은 아니며, ARC는 미소유 참조의 값을 nil으로 설정하지 않습니다.

 

IMPORTANT
unowned 참조는 해당 참조가 항상 할당 해제되지 않은 인스턴스를 참조하는 경우에만 사용하십시오.
인스턴스가 할당 해제된 후 unowned 참조의 값에 액세스하려고 하면 런타임 오류가 발생합니다.

 

다음 예제에서는 은행 고객과 해당 고객의 신용카드를 모델화하는 Customer와 CreditCard의 두 가지 클래스를 정의합니다.

아파트와 사람의 관계와는 약간 다르게, 고객이 신용카드를 가지고 있을 수도 있고 가지고 있지 않을 수도 있지만 신용카드는 항상 고객과 관련되어 있습니다.

이를 나타내기 위한 Customer 클래스에는 card 속성이 optional로 선언되지만, CreditCard 클래스에는 customer 속성이 unowned으로 선언됩니다.

 

또한 새로운 CreditCard 인스턴스는 숫자 값과 고객 인스턴스를 커스텀 이니셜라이저에 전달해야한 생성할 수 있습니다.

이렇게 하면 CreditCard 인스턴스가 생성될 때 관련된 Customer 인스턴스가 항상 유지됩니다.

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) is being deinitialized") }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}

var john: Customer?

john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

고객 인스턴스를 만들고 이를 사용하여 새 신용카드 인스턴스를 초기화하고 해당 고객의 카드 속성으로 할당합니다.

두 인스턴스를 연결했으므로 참조는 다음과 같습니다.

unowned customer 참조로 인해 john 변수가 보유한 강한 참조를 깰 경우 고객 인스턴스에 대한 더 이상의 강한 참조는 없습니다.

고객 인스턴스에 대한 강한 참조가 없으므로 해당 인스턴스는 할당 해제됩니다.

이 경우 신용카드 인스턴스에 대한 강한 참조가 없어지고 신용카드 인스턴스도 할당 해제됩니다.

john = nil
// Prints "John Appleseed is being deinitialized"
// Prints "Card #1234567890123456 is being deinitialized"

위의 마지막 코드는 john 변수가 nil로 설정된 후 고객 인스턴스와 신용카드 인스턴스의 deinit 메시지가 모두 출력됨을 나타냅니다.


감사합니다 :D

반응형