ARC-간단정리

2023. 2. 7. 22:37Swift

반응형

해당 글은 공식문서를 참고했으며, 번역 혹은 개인생각이 들어가 있어 정확하지 않은 정보를 제공할 수 있습니다

참고 문서

ARC(Automatic Reference Counting)

  • Swift는 메모리를 관리하기위해 ARC를 사용한다.
  • ARC는 인스턴스가 필요치 않을 경우 자동으로 메모리에서 할당 해제한다
  • ARC는 참조타입의 경우에만 적용된다

ARC가 어떻게 작동하는지

class Person {
    let name: String

    init(name: String) {
        self.name = name
        print("\(name) 생성")
    }

    deinit {
        print("\(name) 해제")
    }
}
  • 해당 Person클래스는 초기 생성시와 메모리에서 해제되는 시점에서 해당 name을 출력하도록 작성하였다
var a: Person?
var b: Person?
var c: Person?

a = Person(name: "John")
  • 해당 3가지의 Person타입에 a에 John이라는 이름의 Person타입을 선언해주었다. 이럴 경우 Reference Count는 1이된다.
b = a
c = a
  • b와 c의 변수에는 이전에 생성했던 John이라는 Person을 할당시켜주며 John의 Person은 Reference Count가 3이된다.
a = nil
b = nil
c = nil
  • 해당 변수들에 nil을 할당할 경우 참조타입이 하나씩 줄어들면서 최종적으로 마지막 c까지 nil이 되면 John의 Person을 더이상 사용하지 않는게 분명하므로 Persondeinit이 실행되게 된다

Strong Reference Cycles Between Class Instances

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") }
}
  • PersonApartment의 클래스는 각각 서로를 참조하고 있으며 해당 부분은 Reference Counting이 0이 될 경우 deinit이 되게끔 작성되어있다
var john: Person?
var unit4A: Apartment?

john = Person(name: "John")
unit4A = Apartment(unit: "1201")

john?.apartment = unit4A
unit4A?.tenant = john
  • 이 부분은 john과 unit4A가 서로 참조하는 형태로 관계가 맺어져있다.

john = nil
unit4A = nil
  • 만약 이렇게 john과 unit4A가 nil로 할당될 경우 각각 인스턴스가 사라지기 때문에 deinit이 되어야 할 것 같지만 deinit이 실행되지도 않을뿐더러 메모리에도 남아있게 된다.

  • 이유는 기존 Personapartmentunit4A를 참조하고 있으며 마찬가지로 unit4Atennant역시 Person인스턴스를 참조하고 있기 때문에 Reference Counting이 0이 되지 않아 메모리에서 해제되지 않는것이다
john?.apartment = nil
john = nil
unit4A?.tenant = nil
unit4A = nil
  • 이 경우 각각 타입의 프로퍼티에 nil을 할당하고 해당 타입에 nil을 할당해야 메모리에 해제된다.
  • 이것을 해결하기 위해 weak, unowned를 사용한다

Weak Reference

  • 약한 참조는 위의 강한참조 문제를 해결할 수 있다.
  • 다만 약한 참조를 사용하기 위해서는 nil로 변경되어야 하기 때문에 항상 옵셔널 타입의 변수로 설정되어야 한다
class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    weak var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}
  • ApartmentPerson타입을 참조하는 tenant를 약한 참조로 설정해 주었다.

  • 이 경우 johnunit4A는 서로 참조를 하고 있지만 unit4Atenantjohn을 약한참조한다.
john = nil    //print John is being deinitialized
print(unit4A?.tenant)    // print nil

  • 약한 참조이므로 John에 nil이 할당 될 경우 약한 참조로 인해 unit4Atenant가 nil이 되며 johndeinit된다.

Unowned References

  • 미소유 참조는 약한참조와 유사하게 인스턴스를 강한참조 하지는 않는다
  • 미소유 참조의 경우 약한참조에 비해 인스턴스의 수명이 같거나 더 긴 경우에 사용된다
  • 약한 참조와 달리 미소유 참조는 항상 값을 가지도록 해야한다. 즉 옵셔널 값으로 만들어지면 안된다.

미소유 참조는 절대 할당 해제되지 않는 인스턴스일때만 사용한다.
만약 참조하고 있는 인스턴스가 할당 해제되어서 미소유 참조 값에 접근하려 한다면 런타임 에러가 발생하게 된다.

  • 예제로는 고객과 신용카드 객체가 있다.
  • 고객은 신용카드를 안가지고 있을 수 있지만, 신용카드 객체는 항상 고객을 가지고 있어야 하므로 미소유 참조로 고객을 참조하는 형태를 지니기에 적합하다
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")
john?.card = CreditCard(number: 12345678, customer: john!)

  • 이 경우에 john에 nil을 할당 할 경우 Johncard프로퍼티는 각각 메모리에서 헤제된다.
john = Customer(name: "John")
let bcCard = CreditCard(number: 12345678, customer: john!)

john?.card = bcCard

john = nil
  • 다만 위와 같이 CreditCard의 인스턴스를 만들어 John의 프로퍼티에 할당하여 John을 nil로 변경할 경우 john은 메모리에서 해제된다.
print(bcCard.customer)    // Runtime Error Thread 1: signal SIGABRT
  • 단순히 bcCard인스턴스를 접근하는거는 문제가 되지 않지만 해당 인스턴스의 customer을 접근하려고 한다면 위에 말했듯이 런타임에러가 뜨게 된다.

Strong Reference Cycles for Closures

  • 클로저의 경우 캡처로 인하여 강한참조에 대한 문제가 발생할 수 있습니다
class HTMLElement {

    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) is being deinitialized")
    }

}
  • 이 경우 asHTML의 클로저는 해당 클래스의 프로퍼티 nametext를 캡처한다.
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"

  • 해당 클로저는 HTMLElement의 프로퍼티들을 참조하므로 위와 같이 강한참조 문제가 발생하게 된다.

  • 이 경우 paragraph의 인스턴스를 nil로 설정하여도 해당 클로저에 대한 강한참조 문제때문에 클로저는 메모리에서 해제되지 않는다.

  • 위 같은 경우 캡처한 인스턴스가 항상 서로를 참조하고 클래스 내에 정의되어 있기 때문에 같은 시간에 메모리가 해제되도록 미소유 참조를 사용하는것이 좋다.

  • 만약 약한 참조를 사용할 경우, 해당 인스턴스가 항상 옵셔널타입이므로 인스턴스가 nil이 된다면 클로저 역시 nil이 되어 자동으로 해제된다.

lazy var asHTML: () -> String = {
    [unowned self] in
    if let text = self.text {
        return "<\(self.name)>\(text)</\(self.name)>"
    } else {
        return "<\(self.name) />"
    }
}
  • 위 경우 해당 self를 강한참조가 아닌 미소유 참조로 캡처를 하여 사용을 하게 된다면 해당 클래스가 nil이 될 경우 클로저 역시 같이 nil이 되어 메모리에서 해제되게 된다.
반응형

'Swift' 카테고리의 다른 글

JSON Decoding 재활용  (0) 2023.03.03
Swift 컴파일, 컴파일러  (0) 2023.02.14
Closures 간단 정리  (2) 2023.02.06
정규표현식(Swift)  (0) 2023.01.29
KVC(Key-value coding), KVO(Key-value observing)  (0) 2023.01.22