ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • (Swift) 메모리 참조 속성 (strong, week, unowned, optional unowned)
    iOS💖 2024. 10. 16. 16:06

    Apple Documentation 을 보고 정리하였

     

    1. strong 참조

    • default reference type 이다. 
      • var / let 앞에 weak, unowned 가 붙지 않으면 strong reference type 임!!
    • instance 를 확실히 유지 & strong 참조가 유지되는 한, 할당 해제를 허용하지 않음
      • == "강력한 (strong)" 참조
    • ARC 에서 메모리 할당을 해제하지 않는 경우
      1. rc가 0이 되지 않는 상태, 즉, 활성되고 있는 참조가 있는 경우
      2. strong 참조 사이클이 생성된 경우

     

    1) rc (reference counting)
    : rc가 0이 되면, 객체의 메모리 해제

    class Person {
        let name: String
        init(name: String) {
            self.name = name
            print("\(name) is being initialized")
        }
        deinit {
            print("\(name) is being deinitialized")
        }
    }
    
    // Person? : 이러한 변수는 선택적 유형(Person?, Person이 아님)이므로 자동으로 nil 값으로 초기화되고 현재는 Person 인스턴스를 참조하지 않음
    var reference1: Person?
    var reference2: Person?
    var reference3: Person?
    
    reference1 = Person(name: "John Appleseed") // rc 1개
    // Prints "John Appleseed is being initialized"
    
    // 같은 Person 인스턴스를 두 개 의 변수에 더 할당 == rc+2
    reference2 = reference1 // rc 2개
    reference3 = reference1 // rc 3개
    
    reference1 = nil // rc 2개 remains (reference2, reference3)
    reference2 = nil // rc 1개 remains (reference3)
    
    reference3 = nil // rc 0개 == 마지막 strong 참조가 끊어질 때 까지, Person 인스턴스 할당 해제 하지 않음
    // Prints "John Appleseed is being deinitialized"

     

     

    2) 두 객체의 상호 참조
    : 두 객체가 서로를 참조하는 경우가 strong 참조이다. 

    • 두 객체의 상호 참조시, strong 참조 사이클이 생성됨
    • strong 참조 사이클 (strong reference cycle)
      : 두 class instance가 서로에 대한 strong 참조를 보유하고, 각 instance 가 참조하고 있는 instance 를 계속 살아있게 하는 경우 
      • ⭐️이 경우, 두 instance 에 nil을 할당해도, rc가 0이 되지 않고, 메모리 할당이 해제 되지 않음⭐️
      • 두 instance 의 메모리 할당은 유지되며, 끊어질 수 없음
      • ➡️ 앱에서 메모리 누수를 발생 시킴
    • strong 참조 사이클 해결 방법
      • class 간의 관계 중 일부를 strong 참조 대신 weak 참조 /  unowned 참조로 정의해야 한다!
        • weak 참조
          • 다른 instance 보다 lifetime이 짧은(= 메모리 할당 해제 될 수 있는) 경우 사용
          • 아래의 코드 개선
            • Apartment의 lifetime 어느 지점에서 tenant(세입자)가 없는 것이 적절하므로, 
              tenant를 weak 참조로 갖고, 해당 instance 의 메모리 할당을 해제 하는것이 적절하다. 
        • unowend 참조
          • 다른 instance 와 lifetime 이 같거나, 더 긴 경우 사용

     

     

    아래는 strong 참조 사이클이 발생하는 코드

    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 }
        var tenant: Person?
        deinit { print("Apartment \(unit) is being deinitialized") }
    }
    
    // 🔴 1
    var john: Person?
    var unit4A: Apartment?
    
    john = Person(name: "John Appleseed")
    unit4A = Apartment(unit: "4A")
    
    // 🔴 2
    // linking these two instances(john,unit4A) creates a strong reference cycle between them.
    john!.apartment = unit4A
    unit4A!.tenant = john
    
    // 🔴 3
    // The strong reference cycle 는 nil 이 되어도 메모리 할당이 끊어지지 않음 
    john = nil
    unit4A = nil

     

     

     

    🔴 1

     

     


     

     

    🔴 2 _ strong reference cycle 생성됨

     

     


     

     

    🔴 3 _ strong reference cycle 이 생성되었으므로, 메모리 할당 끊기지 않음

     

     

     

     


     

    2. weak 참조

    • 프로퍼티 or 변수 앞에 선언
    • ARC의 참조된 인스턴스를 삭제하는 행위를 막지 못한다.
      • ➡️ weak 참조가 참조하는 동안, 해당 instance의 메모리 할당 해제 될 수 있음! 
      • ➡️ ARC에 의해 메모리 할당 해제된 weak 참조는, ARC에 의해 자동으로 nil 로 설정됨  
    • runtime에도 weak 참조가 nil로 변경 될 수 있어야 하므로,
      • weak 참조는, optional type 이다!

     

     

    위의 strong 참조 사이클이 발생된 코드를, 발생하지 않도록 개선한 코드

    Apartment의 lifetime 어느 지점에서 tenant(세입자)가 없는 것이 적절하므로, 
    tenant를 weak 참조로 갖고, 해당 instance 의 메모리 할당을 해제 하는것이 적절하다. 

     

    : weak var tenant: Person?

    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")
    
    // 🔴 1
    // ⭐️strong 참조때와 같이, 두 인스턴스 (john, unit4A) 간에 string 참조는 이전과 같이 생성된다.⭐️
    john!.apartment = unit4A
    unit4A!.tenant = john
    
    // 🔴 2
    john = nil
    // Prints "John Appleseed is being deinitialized"
    // >> Person 인스턴스에 대한 강력한 참조가 더이상 없으므로, 해당 인스턴스(john)의 할당이 해제되면, 
        // unit4A!.tenant은 nil 이되고, 
        // unit4A 은 여전히 strong 참조가 남는다. 
        
        
    // 🔴 3
    unit4A = nil 
    // 이로써, unit4A의 strong 참조도 끊겼다.

     

     

    🔴 1

     

     


     

     

    🔴 2 _ unit4A!.tenant은 nil, unit4A 은 여전히 strong 참조

     

     

     


     

     

    🔴 3 _ unit4A이 nil이 되면서, 결과적으로 strong 참조도 해제

     

     

     

     

     

     

     

     


    3. unowned 참조

    • lifetime이 다른 instance와 같거나, 더 길때 사용
    • properties or 변수 앞에 선언
    • weak 참조와 다른 점
      • unowned 참조는, 항상 값을 갖을 것을 전제로 한다.
      • ➡️ unowned 참조는, '항상' 참조가 할당 해제되지 않는 instance를 참조할 때만 사용한다.
        • 그렇지 않은 경우, runtime error 발생 ‼️
      • optional type 이 아니기 때문에, ARC는 값을 nil로 설정하지 X

     

     

    unowned 참조를 safe 하게 사용하는 예시

    고객은 신용카드를 가지고 있을 수도 / 않을 수도 있지만, 신용카드는 - 항상 고객과 연결되어있다.

    • CreditCard instance 는 그것이 참조하는 Customer 보다 lifetime이 더 오래 지속되지 않는다.
    • Customer 의 card attribute는 optional type (신용카드가 있을 수 도, 없을 수 도)
    • CreditCard 의 customer는 non-optional type
    • 새로운 CreditCard instance는, number 값, cutomer instance를 CreditCard 생성자에 전달하여야먄 생성 가능!
    • CreditCard 에는 항상 cutomer이 있어야 존재하므로, 항상 값을 갖는 것을 전제로 하고 & Customer이 있어야 CreditCard도 있으니 즉 항상 참조가 끊기지 않는!! unowned 참조로 선언하였음
    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? // 초기값 nil
    
    // 🔴 1
    john = Customer(name: "John Appleseed")
    john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
    
    // 🔴 2
    john = nil
    // Prints "John Appleseed is being deinitialized"
    // Prints "Card #1234567890123456 is being deinitialized"

     

    * UInt64 사용 이유

    : number 프로퍼티의 용량이, 32비트 및 64비트 시스템 모두에서 16자리 카드 번호를 저장할 만큼 충분히 커지도록 한다.

     

     

     

    🔴 1 _ 두 instance 연결 (john의 strong 참조를 끊으면, CreditCard에 대한 strong 참조가 없으니 할당 해제됨

     

     


     

     

    🔴 2

     

     

     

     


     

     

     

     

    optional type 이 아닌 unowned 참조를 optional 하게 할 수 있는데..

    (+) unowned optional 참조

    • 즉, optional 참조를 unowned로 표시할 수 있는데,
      1. 항상 valid (유효한) 객체를 참조하거나
      2. nil 로 설정해야한다
    • ARC 소유권 모델의 관점에서는, unowned optional referece 와 weak reference 는 동일한 context에서 사용될 수 있다.
      • unowned 참조 (= non-optional unowned 참조) 와 동일하게 동작하지만, nil 이 될 수 있음
      • == ARC가 instance 메모리 할당 해제 할 수 있음
      • == 위험성 있고, 메모리 할당 해제시 값이 nil 이 될 수 있으므로, optional type 

     

     

    unowned optional reference 사용 예시

    • 모든 과목(Course)은 어떤 학과(Department)의 일부이므로 학과 속성은 optional type이 아니다.
    • But, 일부 과목에는 권장 후속 과목이 없기 때문에 nextCourse 속성은 optional type
    • 학과는 학과가 제공하는 각 과목에 대한 strong 참조를 유지
    • Course 입장에서 Department 가 존재해야 존재할 수 있으니, optional type 이 아닌, 항상 값을 갖고있는 것을 전제로 하는, 항상 참조가 있는 unowned 참조를 한다.
    • non-optional unowned 참조와 동일하게, nextCourse 가 '항상' 메모리 할당 해제 되지 않은 course 를 참조하도록 하는 것은 개발자의 책임이다.   (= 🔺course 를 삭제 시, 해당 instance를 참조하는 내용도 모두 삭제!🔺)
    class Department {
        var name: String
        var courses: [Course]
        init(name: String) {
            self.name = name
            self.courses = []
        }
    }
    
    
    class Course {
        var name: String
        unowned var department: Department
        unowned var nextCourse: Course?
        init(name: String, in department: Department) {
            self.name = name
            self.department = department
            self.nextCourse = nil
        }
    }
    
    let department = Department(name: "Horticulture")
    
    
    let intro = Course(name: "Survey of Plants", in: department)
    let intermediate = Course(name: "Growing Common Herbs", in: department)
    let advanced = Course(name: "Caring for Tropical Plants", in: department)
    
    
    intro.nextCourse = intermediate
    intermediate.nextCourse = advanced
    department.courses = [intro, intermediate, advanced]

     

     

     

    위의 코드를 그림으로 표현

     

     

     

     

     

     

     

     

     

     

     

     

    ARC > strong, weak, unowned reference 

    https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting/

     

    Documentation

     

    docs.swift.org

     

    ARC 정리글

    https://hortenssiaa.tistory.com/37

     

    (Swift) ARC (Automatic Reference Counting, 자동 참조 계산)

    Apple Documentation 을 보고 정리하였습니다. ARC (Automatic Reference Counting, 자동 참조 계산)앱의 메모리 사용량을 추적, 관리class instance 가 더 이상 필요하지 않을 때, 해당 instance 에서 사용하는 메

    hortenssiaa.tistory.com

    댓글

Designed by black7375.