예를 통해서 ARC가 필요한 상황을 이야기해보자, 아래와 같이 Human Class를 정의하고, miro라는 클래스 인스턴스를 생성했다고 하자. 그리고 miro는 지역변수라고 가정!
class Human {
var name: String?
var age: Int?
init(name: String?, age: Int?) {
self.name = name
}
}
let miro = Human(name: "Miro")
그러면 아래와 같이 miro는 지역변수 이므로 스택에 할당이되고, Human 인스턴스는 힙에 할당이된다.
그리고 아래와 같이 클론을 만들어서 주소값을 복사해보자.
그리고 만약 함수 종료 시점에 miro와 clone이 사라지게 된다면 아래와 같은 그림과 같이 될 것이다.
그렇다면 힙에 있는 인스턴스는 누가 메모리 해제를 해줄까? 바로 ARC가 해준다!
어떠한 방식으로 ARC가 작동하는 알아보자
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1 = Person(name: "John Appleseed") // reference count : 1
// Prints "John Appleseed is being initialized"
reference2 = reference1 // reference count : 2
reference3 = reference1 // reference count : 3
reference1 = nil // reference count : 2
reference2 = nil // reference count : 1
reference3 = nil // reference count : 0
// Prints "John Appleseed is being deinitialized"
이를 통해서 ARC는 인스턴스를 생성하면 참조 카운트를 추적하고 더 이상 필요하지 않을 때(참조 카운트 = 0)일 때에 인스턴스를 메모리에서 해제한다는 것을 알 수 있었습니다. → By ARC
위와 같은 강한 참조 시 참조를 한 인스턴스가 해제되었음에도 계속해서 참조를 유지하는 문제가 발생하는데, 아래의 코드를 통해서 해당 문제를 알아보자.
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment? // 옵셔널로 초깃값은 nil
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person? // 옵셔널로 초깃값은 nil
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed") // Person reference count : 1
unit4A = Apartment(unit: "4A") // Apartment reference count : 1
john!.apartment = unit4A // Apartment reference count : 2
unit4A!.tenant = john // Person reference count : 2
그러면 아래와 같은 그림으로 강한 참조를 나타낼 수 있습니다.
그리고 난 후, 변수에 데이터를 할당 해제하게 되면, 강한 참조가 사라지게 되면서 참조 카운트는 1이 됩니다.
john = nil // Person reference count : 1
unit4A = nil // Apartment reference count : 1
→ 즉, Person instance와 Apartment 인스턴스 사이의 강한 참조가 유지되면서 불필요한 데이터가 메모리 상에서 해제되지 않고 유지가 되는 문제가 발생합니다. 즉 서로 instance끼리 참조를 하고 있어 RC가 0이 되지 않은 것이다!
‼️ Person 객체와 Apartment 객체가 몇번이나 할당이 되었는 지를 통하여 RC를 계산한다!
→ 아래와 같이 순한 참조를 일으키는 프로퍼티 앞에 weak를 붙혀주면 된다.
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment? // 옵셔널로 초깃값은 nil
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person? // 옵셔널로 초깃값은 nil
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed") // Person reference count : 1
unit4A = Apartment(unit: "4A") // Apartment reference count : 1
john!.apartment = unit4A // Apartment reference count : 2
unit4A!.tenant = john // Person reference count : 1
john = nil // Person reference count : 0, Apartment 4A is being deinitialized
unit4A = nil // Apartment reference count : 0, John Appleseed is being deinitialized
→ unowned로 선언된 변수가 가르키던 인스턴스가 메모리에서 먼저 해제가 된 경우, 해당 변수에 접근을 하려고 하면 에러가 발생하게 된다.
그리고, weak는 런타임에 nil이 될 수 있기에 optional로 선언되어야하고, var로 선언되어야한다.
그러나, unowned의 경우 위에서 설명한 바와 같이 참조 도중에 해당 인스턴스가 메모리에서 사라질 일이 없다고 확신하기에 옵셔널로 선언하지 않는다!
아래와 같이 weak의 경우 인스턴스를 nil로 할당할 수 있다.
그러나, unowned의 경우 weak와는 다르게 unowned로 설정된 값을 nil로 설정하지 않는다!(계속 메모리 값을 가지고 있어서 접근하면 error가 난다!)
이 또한, 예를 통해서 알아보자.
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!)
아래와 같이 Customer instance의 RC는 1, CreditCard instance의 RC은 1이다.
그리고 아래와 같이 nil을 할당을 해주자.
john = nil
그러면 아래와 같이 Customer instance의 RC는 0이 되고(메모리에서 해제가 된다), CreditCard instance의 RC도 0이 된다.
만약 위의 코드 말고 weak와 동일한 코드에서 weak와 unowned를 가정하고 john에게 nil을 할당하고 unit4A.tenant에 접근한다고 가정해보자.
// **weak**
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment? // 옵셔널로 초깃값은 nil
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person? // 옵셔널로 초깃값은 nil
deinit { print("Apartment \(unit) is being deinitialized") }
}
// **unowned**
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment? // 옵셔널로 초깃값은 nil
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
unowned let 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
john = nil
unit4A.tenant
https://babbab2.tistory.com/27
https://zeddios.tistory.com/1213