Swift Automatic Reference Counting
Model the lifetime of objects and their relationships.
1. Automatic Reference Counting π©βπ»
Swift λ Automatic Reference Counting (ARC)
λ₯Ό μ¬μ©ν΄ μ±μ λ©λͺ¨λ¦¬ μ¬μ©μ κ΄λ¦¬νκ³ μΆμ νλ€. λλΆλΆμ κ²½μ° Swift μμ κ°λ°μλ
λ©λͺ¨λ¦¬λ₯Ό κ΄λ¦¬ν νμκ° μλ€. μ΄μ λν΄ Apple μ μ΄λ κ² λ§νλ€. just work
.
ARC λ Class Instance
κ° λ μ΄μ νμνμ§ μμ λ λ©λͺ¨λ¦¬ ν λΉμ ν΄μ (free up)
νλ€
(Deinitialization μ΄ νΈμΆλ¨μ μλ―Έ).
κ·Έλ¬λ μΌλΆ κ²½μ° ARC λ λ©λͺ¨λ¦¬λ₯Ό κ΄λ¦¬νκΈ° μν΄ μ½λ κ΄κ³μ λν μΆκ° μ 보λ₯Ό μꡬνλ€. Swift μμ ARC λ₯Ό μ¬μ©νλ κ²μ Objective-C μμ ARC μ¬μ©μ λν Transitioning to ARC Release Notes μμ μ€λͺ ν μ κ·Ό λ°©μκ³Ό μ μ¬νλ€.
Reference counting
μClass Instance
μλ§ μ μ©λλ€. Structures μ Enumerations λ Value Typesμ΄λ€.
2. How ARC Works π©βπ»
Class μ new Instance
κ° μ겨λ λλ§λ€, ARC λ *Instance μ μ 보λ₯Ό μ μ₯νκΈ° μν΄ λ©λͺ¨λ¦¬ μ²ν¬λ₯Ό ν λΉ
(allocates a chunk of memory)
νλ€. μ΄ λ©λͺ¨λ¦¬λ Instance μ Type μ λν μ 보
μ Instance μ μ°κ΄λ Stored Properties
μ κ°μ λν μ 보(pointer)
λ₯Ό κ°λλ€.
λ°λλ‘ λ μ΄μ Class Instance κ° νμνμ§ μμ κ²½μ°, ARC λ Instance μ μ¬μ©λκ³ μλ λ©λͺ¨λ¦¬ ν λΉ(free up the memory)μ ν΄μ
ν΄
λ€λ₯Έ νλ‘μΈμ€κ° μ¬μ©ν μ μλλ‘νλ€.
λ§μ½ ARC κ° μμ§ μ¬μ©μ€μΈ Instance μ λ©λͺ¨λ¦¬ ν λΉμ ν΄μ νλ©΄, λ μ΄μ Instance μ Properties, Methods μ μ κ·Όν μ μμ΄
μ±μ crash κ° λ°μ
νλ€.
λ°λΌμ ARC λ μμ§ μ¬μ©μ€μΈ Instances κ° λ©λͺ¨λ¦¬ ν΄μ λμ§ μλλ‘, κ° Class Instance κ° μΌλ§λ λ§μ Properties, Constants,
Variables λ₯Ό μ°Έμ‘°(referring)νκ³ μλμ§ μΆμ
ν΄ λ¨ νλμ μ°Έμ‘°(reference)λΌλ μ ν¨νλ€λ©΄ Instance μ ν λΉμ
ν΄μ (deallocate)νμ§ μλλ€
.
μ΄κ²μ κ°λ₯νλλ‘ νκΈ° μν΄ ARC λ Class Instance λ₯Ό Properties, Constants, Variables μ ν λΉν λλ§λ€ μ΄λ€ μ¬μ΄μ κ°ν μ°Έμ‘°
(strong reference)λ₯Ό λ§λ λ€
. βstrongβ μ΄λΌλ λ¨μ΄κ° μ¬μ©λ μ΄μ λ ν΄λΉ Instances κ° λ¨μμλ ν ARC λ λ©λͺ¨λ¦¬ ν λΉ ν΄μ λ₯Ό
νμ©νμ§ μκΈ° λλ¬Έμ΄λ€.
3. ARC in Action π©βπ»
ARC μ λμμ νμΈνκΈ° μν΄ Person μ΄λΌλ Class λ₯Ό νλ μμ±νλ€.
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
λ€μμΌλ‘ Person?
Types μ λ³μλ₯Ό 3κ° μμ±νλ€. Optional Types μ΄λ―λ‘ ν΄λΉ λ³μ 3κ°λ nil
value λ‘ μ΄κΈ°ν λλ€.
var reference1: Person?
var reference2: Person?
var reference3: Person?
new Person
instance λ₯Ό νλ μμ±ν΄ reference1
λ³μμ ν λΉνλ€.
reference1 = Person(name: "John Appleseed")
John Appleseed is being initialized
μ΄μ reference1
λ³μκ° Person(name: "John Appleseed")
instance λ₯Ό Strong References λ‘ κ°λλ€. λ°λΌμ ARC λ
μ΄ Person(name: "John Appleseed")
μ λν Strong References λ₯Ό +1 μμΌ 1κ°λ₯Ό κΈ°μ΅
ν΄ μ΄ Instance κ° λ©λͺ¨λ¦¬μ
μ μ§λκ³ , deallocated λμ§ μλλ‘ νλ€.
reference2 = reference1
μ΄μ reference2
λ³μ μμ Person(name: "John Appleseed")
instance λ₯Ό Strong References λ‘ κ°μ Έ μ΄λ€ μ¬μ΄μλ
Strong References κ° μμ±λμλ€. λ°λΌμ ARC λ Person(name: "John Appleseed")
μ λν Strong References λ₯Ό
+1 μμΌ 2κ°λ₯Ό κΈ°μ΅
νλ€.
κ·Έλ¦¬κ³ μ¬κΈ°μ μ€μν κ²μ
new Instance
λ₯Ό μμ±νλ κ²μ΄ μλOriginal Instance μ Reference λ₯Ό 곡μ
νλ κ²μ΄κΈ° λλ¬Έμ Initializer λ νΈμΆλμ§ μλλ€.
reference3 = reference1
λ§μ°¬κ°μ§λ‘ μ΄μ Person(name: "John Appleseed")
μ λν Strong References λ 3κ°κ° μμ±λμλ€.
3κ°μ Strong References μ€ Original Reference λ₯Ό ν¬ν¨ν΄ 2κ°λ₯Ό λμ΄λ³΄μ(break).
reference1 = nil
reference2 = nil
ARC λ
Person(name: "John Appleseed")
μ λν Strong References λ₯Ό-2 μμΌ 1κ°λ₯Ό κΈ°μ΅
νλ€. λ°λΌμ μμ§ μ΄Instance κ° λ©λͺ¨λ¦¬μ μ μ§λκ³ , deallocated λμ§ μλλ‘ νλ€
.
λ§μ§λ§ λ¨μ Strong References μμ λμ΄λ³΄μ.
reference3 = nil
John Appleseed is being deinitialized
ARC λ
Person(name: "John Appleseed")
μ λν Strong References λ₯Ό-1 μμΌ μ‘΄μ¬νμ§ μμμ νμΈ(zero strong references)
νλ€.
λ°λΌμ μ΄μ Instance λ deallocated λμ΄ Deinitializer κ° νΈμΆλλ€.
4. Strong Reference Cycles Between Class Instances π©βπ»
μμμ ARC κ° μ΄λ»κ² λμνκ³ , Instance λ₯Ό λ©λͺ¨λ¦¬μ μ μ§νλμ§ νμΈνλ€.
μ΄λ²μλ Strong References κ° μ λλ‘ zero strong references μ λλ¬νμ§ μλ μ½λμ μλ₯Ό λ³΄λ € νλ€. μ΄λ λ κ°μ Classes κ°
μλ‘μ λν Strong References λ₯Ό κ°λ κ²½μ° λ°μ
νλ€. λ Instances λ₯Ό λμμ ν΄μ (deallocate)ν μ μμ΄ κ° Instances λ μλ‘λ₯Ό
μ μ§μν¨λ€.
ν΄λΉ Case λ₯Ό νμΈνκΈ° μν΄ Person κ³Ό Apartment λΌλ Classes λ₯Ό μλμ κ°μ΄ μμ±νλ€.
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") }
}
Person
class λ μ΄κΈ°κ°μΌλ‘nil
μ κ°λApartment?
λ₯Ό Properties λ‘ κ°λλ€.Apartment
class λ μ΄κΈ°κ°μΌλ‘nil
μ κ°λPerson?
μ Properties λ‘ κ°λλ€.
μμ λ§μ°¬κ°μ§λ‘ λ³μλ₯Ό μ μΈνλ€.
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
λ³μ unit4A
μ Apartment(unit: "4A")
instance λ₯Ό Strong References λ‘ κ°λλ€.
Person μ Apartment λ₯Ό κ°λλ‘, Apartment λ Person μ κ°λλ‘ ν μ μλ€. μ΄ λμ΄ μλ‘μ Instances λ₯Ό Strong References λ‘
κ°λλ‘ ν΄λ³΄μ.
john?.apartment = unit4A
unit4A?.tenant = john
μ΄μ Person(name: "John Appleseed")
μ λ³μ john
κ³Ό Apartment(unit: "4A")
instance μ λ³μ property tenant
μ
μν΄ μ°Έμ‘°λμ΄ ARC λ 2κ°μ Strong References κ° μ‘΄μ¬ν¨μ νμΈνλ€. λ°λμ κ²½μ°λ λ§μ°¬κ°μ§λ‘ Apartment(unit: "4A")
instance
μμ ARC λ 2κ°μ Strong References κ° μ‘΄μ¬ν¨μ νμΈνλ€.
λ³μ john
κ³Ό unit4A
κ° κ°λ Strong References λ₯Ό λμ΄λ³΄μ.
john = nil
unit4A = nil
// Nothing
μλ‘κ° μλ‘λ₯Ό Strong References λ‘ μ°Έμ‘°νκ³ μκΈ° λλ¬Έμ λ Instances λ μ λλ‘ Zero Strong References
μ λλ¬ν μ μλ€.
λ§μ½ μ΄κ±Έ λμ΄λ΄λ €λ©΄ μλ‘μ λν Strong References λ₯Ό λ¨Όμ λμ΄μΌνλ€.
john?.apartment = nil
unit4A?.tenant = nil
john = nil
unit4A = nil
Apartment 4A is being deinitialized
John Appleseed is being deinitialized
νμ§λ§ μ΄ λ°©λ²μ μνν λ°©λ²μ΄λ€. κ°λ°μκ° μ΄λ₯Ό λμΉκ±°λ λ‘μ§ μμμ λλ μκΈ°μΉ λͺ»ν μλ¬ λ±μΌλ‘ μΈν΄ λ³μ john
μ΄λ unit4A
κ° κ°λ
Strong References λ§ λμ΄μ§ κ²½μ° λμ΄μ λ©λͺ¨λ¦¬λ₯Ό ν΄μ ν μ μλ μνκ° λλ―λ‘ Memory Leak
μ΄ λ°μνλ€.
5. Resolving Strong Reference Cycles Between Class Instances π©βπ»
1. How Resolve Strong Reference Cycles
Swift λ μμ κ°μ Strong Reference Cycles λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ 2κ°μ§ λ°©λ² Weak References
μ Unowned References
λ₯Ό
μ 곡νλ€. μ΄λ₯Ό μ¬μ©ν΄ μ°Έμ‘°νλ©΄ Reference Cycles μ ν Instance κ° κ°ν μ μ§ μμ΄ λ€λ₯Έ Instance λ₯Ό μ°Έμ‘°ν μ μλ€. κ·Έλ¬λ©΄
Reference Cycles μ νμͺ½μ μ°Έμ‘°κ° Strong References κ° μλκ² λλ―λ‘ Strong Reference Cycles μμ΄ μλ‘λ₯Ό μ°Έμ‘°ν μ
μκ³ , νμ μμ΄μ‘μ λ μ°κ²° κ³ λ¦¬λ₯Ό λμ΄λ΄κ³ λ©λͺ¨λ¦¬λ₯Ό ν΄μ ν μ μκ² λλ€.
- μ°Έμ‘°νλ Instance κ°
Short Lifetime
μ κ°λ κ²½μ°Weak References
λ₯Ό μ¬μ©νλ€.- μ°Έμ‘°νλ Instance κ°
Same Lifetime
λλLong Lifetime
μ κ°λ κ²½μ°Unowned References
λ₯Ό μ¬μ©νλ€.
μ΄λ₯Ό μ΄μ©νλ©΄ Strong References μμ΄ μλ‘μ λν Reference Cycles
λ₯Ό κ°μ§ μ μλ€.
2. Weak References
μ μμ μ κ²½μ° Apartment λ tenant κ° μμ μλ, μμ μλ μλ€κ³ νμ. κ·Έλ λ€λ©΄ Apartment μ λΉν΄ tenant μ ν λΉλλ Person μ
Lifetime μ΄ Short Lifetime μ κ°μ§λ―λ‘ tenant λ₯Ό Weak References
λ‘ λ°κΎΈλ κ²μ΄ μ μ νλ€.
Weak References λ Instance λ₯Ό κ°νκ² μ μ§(strong hold)νμ§ μλ μ°Έμ‘°μ΄λ―λ‘ ARC λ Instance κ° ν΄μ (deallocate)
λλ κ²μ
λ§μ§ μλλ€.
Property Observers λ
ARC κ° Weak Reference μ nil μ μ€μ (set)ν λ νΈμΆλμ§ μλλ€
.
μλ μμ λ μμ κ±°μ λμΌνμ§λ§, μ΄λ²μλ Apartment μ tenant λ₯Ό Weak Reference
λ‘ μ μΈνλ€.
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")
λ³μ unit4A
μ Apartment(unit: "4A")
instance λ₯Ό Strong References λ‘ κ°λλ€.
μ μμ μ λ§μ°¬κ°μ§λ‘ μ΄ λμ΄ μλ‘μ Instances λ₯Ό μ°Έμ‘°νλλ‘ Reference Cycles λ₯Ό λ§λ€μ΄λ³΄μ.
john?.apartment = unit4A
unit4A?.tenant = john
μμ λ§μ°¬κ°μ§λ‘ λ³μ john
κ³Ό unit4A
κ° κ°λ Strong References λ₯Ό λμ΄λ³΄μ.
- Set
nil
tounit4A
variable
print(unit4A as Any) // Optional(__lldb_expr_13.Apartment)
unit4A = nil
print(unit4A as Any) // nil
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print(john?.apartment as Any) // Optional(__lldb_expr_13.Apartment)
}
λ³μ unit4A
μ Strong References λ λμ΄μ‘μ§λ§ Person(name: "John Appleseed")
μ΄ Apartment(unit: "4A")
instance λ₯Ό Strong References λ‘ κ°κ³ μκΈ° λλ¬Έμ ν΄μ (deallocate)
λμ§ μλλ€.
- Set
nil
tojohn
variable
κ·Έλ λ€λ©΄ μ²μλΆν° λ€μ μμν΄μ μ΄λ²μλ λ³μ john
μ΄ κ°λ Strong References λ₯Ό λμ΄λ³΄μ.
print(john as Any) // Optional(__lldb_expr_17.Person)
john = nil
print(john as Any) // nil
John Appleseed is being deinitialized
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print(unit4A?.tenant as Any) // nil
}
λ³μ john
μ Strong References κ° λμ΄μ§μ λμ΄μ Strong References κ° λ¨μ§ μμ
Person(name: "John Appleseed")
μ Deinitializers λ₯Ό νΈμΆ ν ν΄μ (deallocate)
λμλ€.
Person(name: "John Appleseed")
μ΄ ν΄μ λμ΄ μ΄μ Apartment(unit: "4A")
λ νλμ Strong Referencesλ§ λ¨κ² λμλ€.
μ΄μ Apartment(unit: "4A")
μμ ν΄μ κ° κ°λ₯νλ€.
unit4A = nil
Apartment 4A is being deinitialized
Garbage Collection
μ μ¬μ©νλ μμ€ν μμλ Garbage Collection μ΄trigger
λ λλ§ Strong References κ° μλ κ°μ²΄κ° deallocated λκΈ° λλ¬Έμ Simple Caching Mechanism μ ꡬννλλ°Weak Pointer
κ° μ¬μ©λλ κ²½μ°κ° μλ€.
μ¦,Weak Pointer λ Garbage Collection μ΄ trigger λκΈ° μ κΉμ§ μ°Έμ‘°κ° κ°λ₯
νλ€.νμ§λ§ Swift μ
ARC
λ μ’λ tight νκ² λ©λͺ¨λ¦¬λ₯Ό κ΄λ¦¬νλ€. ARC λλ§μ§λ§ Strong References κ° μ κ±°λλ μ¦μ deallocated λμ΄ Weak References λ μ¦μ μ°Έμ‘°κ° λΆκ°λ₯
νλ€.
3. Unowned References
Weak References μ λ§μ°¬κ°μ§λ‘ Unowned References
λ μ°Έμ‘°νλ Instance λ₯Ό κ°νκ² μ μ§(strong hold)νμ§ μλλ€. κ·Έλ¬λ
Weak References μ λ€λ₯΄κ² Unowned References λ μ°Έμ‘°νλ Instance μ μλͺ
μ΄ κ°κ±°λ(Same Lifetime) λ κΈ΄(Long Lifetime)
κ²½μ° μ¬μ©νλ€. Weak References μ λ§μ°¬κ°μ§λ‘ Properties λλ Variables μ μΈ μ μ unowned
keyword μμΉμμΌ μ μνλ€.
Weak References μ λ¬λ¦¬ Unowned References λ νμ κ°μ κ°μ§ κ²μΌλ‘ μμλλ€. κ²°κ³Όμ μΌλ‘ Unowned References λ
Value λ₯Ό Optional λ‘ λ§λ€μ§ μκ³
, ARC λ Unowned References μ κ°μ nil λ‘ μ€μ νμ§ μλλ€
.
References κ° νμ deallocated λμ§ μμ Instance λ₯Ό μ°Έμ‘°νλ€κ³ νμ νλ κ²½μ°
μλ§Unowned References
λ₯Ό μ¬μ©ν΄μΌνλ€.
μ¦, Strong References κ° μλμ΄μ ν΄μ κ° κ°λ₯νλ°, Instance κ° deallocated λ ν μ κ·Όν κ²½μ°Runtime Error
κ° λ°μνκΈ° λλ¬Έμ΄λ€.
λ€μ μμ λ Customer
μ CreditCard
λΌλ λ Classes λ₯Ό λͺ¨λΈλ‘ νλ€. μ΄ μμ λ μμμμ Person κ³Ό Apartment λͺ¨λΈκ³Ό μ‘°κΈ λ€λ₯Έ κ΄κ³λ₯Ό
κ°λλ€. μ΄ λ°μ΄ν° λͺ¨λΈμμ Customer λ CreditCard λ₯Ό κ°μ§κ³ μκ±°λ κ°μ§κ³ μμ§ μμ μ μμ§λ§, CreditCard λ νμ Customer μ
μ°κ²°λμ΄μλ€.
μμ λͺ¨λΈκ³Ό λΉκ΅ν΄λ³΄μ.
1 ) Person κ³Ό Apartment λͺ¨λΈ
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") }
}
- Person : Apartment(=apartment) μμ΄ μ‘΄μ¬ν μ μλ€.
init(name:)
&var apartment: Apartment?
- Apartment : Person(=tenant) μμ΄ μ‘΄μ¬ν μ μλ€.
init(unit:)
&var tenant: Person?
- κ·Έλ¦¬κ³ Person μ Lifetime μ΄ Apartment μ Life Cycles λ³΄λ€ μ§§λ€.
λ°λΌμ Lifetime μ΄ κΈ΄μͺ½μΈ Apartment κ° Short Lifetime μ κ°λ Person μ μ°Έμ‘°ν λ
week
λ₯Ό λΆμ¬week var tenant: Person?
λ₯Ό λ§λ€μ΄ μ€λ€.
2 ) Customer μ CreditCard λͺ¨λΈ
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") }
}
- Customer : CreditCard(=card) μμ΄ μ‘΄μ¬ν μ μλ€.
init(name:)
&var card: CreditCard?
- CreditCard : Customer(=customer) μμ΄ μ‘΄μ¬ν μ μλ€.
init(number:customer:)
&let customer: Customer
- κ·Έλ¦¬κ³ Customer μ Lifetime μ΄ CreditCard μ Lifetime λ³΄λ€ κΈΈκ±°λ κ°μΌλ©°,
CreditCard λ Customer μ μ’ μμ
μ΄λ€.
λ°λΌμ Lifetime μ΄ μ§§κ±°λ κ°μΌλ©° Customer μ μ’ μμ±μ κ°λ CreditCard κ° Long Lifetime μ κ°λ Customer λ₯Ό μ°Έμ‘°ν λ
unowned
λ₯Ό λΆμ¬unowned let customer: Customer
λ₯Ό λ§λ€μ΄ μ€λ€.
CreditCard λ Customer λ₯Ό νμ κ°κ³ μμ΄μΌ νλ€λ μ’ μμ±μ΄ μκΈ° λλ¬Έμ Strong Reference Cycles λ₯Ό νΌνκΈ° μν΄ νμ
Unowned References
λ‘ μ μνλ€.
var john: Customer?
john = Customer(name: "John Appleseed")
μ΄μ λ³μ john
μ Customer(name: "John Appleseed")
instance λ₯Ό Strong References λ‘ κ°λλ€.
κ·Έλ¦¬κ³ μ΄μ Customer(name: "John Appleseed")
μ΄ μ‘΄μ¬νλ―λ‘ Customer μ μ’
μμ±μ κ°λ CreditCard
instance λ₯Ό μμ±ν μ μλ€.
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
μ΄μ λ³μ john
μ΄ κ°λ Strong References λ₯Ό λμ΄λ³΄μ.
john = nil
ARC λ Customer(name: "John Appleseed")
κ° zero strong references μ λλ¬νμμ νμΈνκ³ Instance λ₯Ό deallocated μν€λ©°,
Customer μ μ’
μμ±μ κ°μ§λ©° Customer μ Properties λ‘μ¨ μ‘΄μ¬νλ CreditCard μμ deallocated λλ€.
John Appleseed is being deinitialized
Card #1234567890123456 is being deinitialized
μ μμ λ μ΄λ»κ²
Safe Unowned References
λ₯Ό μ¬μ©νλμ§ λ³΄μ¬μ€λ€.νμ§λ§ Swift λ μ±λ₯ μμ μ΄μ λ‘
Runtime Safety Checks
λ₯Ό λΉνμ±ν ν μ μλUnsafe Unowned References
μμ μ 곡νλ€. λμ Unstructured Concurrency μ λ§μ°¬κ°μ§λ‘ μμ ν μ± μ(completely responsibility for correctness)μ΄ μ¬μ©μμκ² μ£Όμ΄μ§λ€.
Unsafe Unowned References λ‘ μ½λλ₯Ό μμ±νκ³ , μ°Έμ‘°νλ Instance κ° deallocated λ κ²½μ°, Unsafe Unowned References λ μ΄λ₯Ό μ μ μμ΄ κΈ°μ‘΄μ κ°μ§κ³ μλ λ©λͺ¨λ¦¬ μ£Όμ(Pointer)λ₯Ό μ΄μ©ν΄ μμ νμ§ μμ μ κ·Όμ νκ² λλ€.
4. Unowned Optional References
μ μμ μμλ Unowned References
κ° Non-Optional μ΄μλ€. μ΄λ²μλ
Optional Types μΈ Unowned Optional References
μ λν΄ μμλ³Έλ€.
ARC Ownership Model
μμUnowned Optional References
μWeak References
λ κ°μcontext
μμ μ¬μ©λ μ μλ€.
μ°¨μ΄μ μ Unowned Optional References λ₯Ό μ¬μ©ν λValid Object λ₯Ό μ°Έμ‘°
νκ±°λnil λ‘ μ€μ
λμ΄μλμ§ νμΈν΄μΌνλ€.κ·Έλ¦¬κ³ κ°μ₯ μ€μν κ²μ Unstructured Concurrency μ λ§μ°¬κ°μ§λ‘ μμ ν μ± μ(completely responsibility for correctness)μ΄ μ¬μ©μμκ² μ£Όμ΄μ§λ€.
λ€μμ νκ΅μ νΉμ κ³Όμ μ 곡λλ κ°μλ₯Ό μΆμ νλ μμ λ€.
class Department {
var name: String
var course: [Course]
init(name: String) {
self.name = name
self.course = []
}
deinit { print("Department '\(name)' is being deinitialized") }
}
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
}
deinit { print("Course '\(name)' is being deinitialized") }
}
Department
λ κ³Όμμ μ 곡νλ κ°μμ λν΄ κ°ν μ°Έμ‘°λ₯Ό κ°λλ€. κ·Έλ¦¬κ³ ARC Ownership Model μμ Department λ κ°μλ₯Ό μμ
νκ³ μκ³ ,
Course
λ department μ nextCourse λΌλ 2κ°μ Unowned References
λ₯Ό κ°λλ€.
κ·Έλ¦¬κ³ Department μ Lifetime μ΄ Course μ Lifetime λ³΄λ€ κΈΈκ±°λ κ°μΌλ©°, Course μ department λ Department μ
μ’
μμ
μ΄λ―λ‘ Optional μ΄ μλλ€. νμ§λ§ Course μ nextCourse λ μ‘΄μ¬ν μλ, μμ μλ μκΈ° λλ¬Έμ Optional
μ΄λ€.
var department: Department?
var intro: Course?
var intermediate: Course?
var advanced: Course?
department = Department(name: "Horticulture")
intro = Course(name: "Survey of Planets", in: department!)
intermediate = Course(name: "Growing Common Herbs", in: department!)
advanced = Course(name: "Caring for Tropical Plants", in: department!)
intro?.nextCourse = intermediate!
intermediate?.nextCourse = advanced!
department?.course = [intro!, intermediate!, advanced!]
μμ κ°μ΄ Horticulture κ³Όμ 3κ°μ κ°μλ₯Ό κ°μ€νκ³ , λ±λ‘ν κ²°κ³Όλ₯Ό κ·Έλ¦ΌμΌλ‘ νννλ©΄ λ€μκ³Ό κ°λ€.
νλ² κ°κ°μ λ³μλ€μ μΆλ ₯ν΄λ³΄μ. μ°μ κ°μλ λ€μκ³Ό κ°μ΄ νμΈλλ€.
let printCourse = { (variableName: String, course: Course) in
print("""
[\(variableName)]
Class : \(course)
Name : \(course.name)
Department : \(course.department)
Next Course : \(course.nextCourse as Any)
""")
}
printCourse("intro", intro!)
printCourse("intermediate", intermediate!)
printCourse("advanced", advanced!)
[intro]
Class : __lldb_expr_131.Course
Name : Survey of Planets
Department : __lldb_expr_131.Department
Next Course : Optional(__lldb_expr_131.Course)
[intermediate]
Class : __lldb_expr_131.Course
Name : Growing Common Herbs
Department : __lldb_expr_131.Department
Next Course : Optional(__lldb_expr_131.Course)
[advanced]
Class : __lldb_expr_131.Course
Name : Caring for Tropical Plants
Department : __lldb_expr_131.Department
Next Course : nil
κ³Ό μ 보λ μΆλ ₯ν΄λ³΄μ.
print("[department] : \(department!), \(String(describing: department!.name)), \(String(describing: department!.course))")
[department] : __lldb_expr_131.Department, Horticulture, [__lldb_expr_131.Course, __lldb_expr_131.Course, __lldb_expr_131.Course]
μ Unowned References μ λ¬λ¦¬ μ¬μ©μκ° Classes μ¬μ΄μ References λ₯Ό κ΄λ¦¬νκ³ deallocated μν€λ κ²μ λν΄ μ± μμ λ€νμ§ λͺ»νμ λ μ΄λ€ μΌμ΄ λ°μνλμ§ νμΈν΄λ³Έλ€.
- Unsafe Unowned References - error case
department = nil
print(department as Any) // nil
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
printCourse("intro", intro!) // error
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("1")
print(intro!.name)
print(intro!.department) // error
print("2")
}
1
Survey of Planets
μ°Έμ‘°νλ Instance κ° deallocated λμμΌλ, Unsafe Unowned References λ μ΄λ₯Ό μ μ μμ΄ κΈ°μ‘΄μ κ°μ§κ³ μλ λ©λͺ¨λ¦¬ μ£Όμ(Pointer)λ₯Ό μ΄μ©ν΄ μμ νμ§ μμ μ κ·Όμ νκ³ , κ°μ λ°μμ€μ§ λͺ»ν΄ λμ΄μ μ§νμ΄ λμ§ μκ³ λ©μΆ°λ²λ Έλ€. λ§μ½, λ€λ₯Έ νλ‘μΈμ€μ μν΄ ν΄λΉ λ©λͺ¨λ¦¬ μ£Όμμ κ°μ΄ μ μ₯λμμΌλ μμν κ²κ³Ό λ€λ₯Έ κ°μ΄ λ€μ΄κ° μκ³ κ·Έκ±Έ κ°μ Έμ¬ κ²½μ°λ Runtime Error λ‘ μ΄μ΄μ§ μλ μλ μ¬κ°ν λ¬Έμ λ₯Ό λ°μμν¬ μ μλ€.
- Unsafe Unowned References - success case
μμ κ°μ λ°μνλ κ²μ λ§κΈ° μν΄ Course κ° μΌλΆ deallocated λ κ²½μ°, κ·Έ Course λ₯Ό μ°Έμ‘°νλ κ²λ€μ λ¨Όμ λμ΄μΌνλ©°,
λ§μ½ Department κ° deallocated λ κ²½μ°, Department μ μ’
μμ±μ κ°λ λͺ¨λ Course κ° unowned var department
property μ
μ κ·Όνμ§ λͺ»νλλ‘ νκ±°λ λͺ¨λ Course λ₯Ό ν¨κ» deallocated ν΄μΌνλ€.
do {
department = nil
advanced = nil
intermediate = nil
intro = nil
}
Department 'Horticulture' is being deinitialized
Course 'Survey of Planets' is being deinitialized
Course 'Growing Common Herbs' is being deinitialized
Course 'Caring for Tropical Plants' is being deinitialized
Optional Value
κΈ°λ³Έ Types λSwift Standard Library μ Enumeration μΈ Optional
μ΄λ€.
κ·Έλ¬λ Optional μ Value Types μunowned
λ₯Ό marked ν μ μλ€λ κ·μΉμ λν΄ μμΈλ€.Class λ₯Ό Wrapping ν Optional μ
Swift Standard Library μ Enumeration μΈ Optional
Types μ΄λ―λ‘ Container κ° Value Type κ° λλ€. μ¦,Reference Counting
μ μ¬μ©νμ§ μμΌλ―λ‘, Strong References λ₯Ό Optional λ‘ μ μ§ν νμκ° μλ€.
5. Unowned References and Type! Properties
μμμ Strong Reference Cycles λ₯Ό λκΈ° μν 2κ°μ§ λ°©λ²(Week References, Unowned References)μ λν΄ λ€λ£¨μλ€.
1 ) 2κ°μ Properties κ° λͺ¨λ nil μ νμ©νλ μΌμ΄μ€
Person κ³Ό Apartment μμ λ 2κ°μ Properties κ° λͺ¨λ nil μ νμ©νλ κ²½μ°μ Strong Reference Cycles μ΄ λ°μν κ°λ₯μ±μ΄
μλ μν©μ 보μ¬μ€λ€. μ΄ μλ리μ€λ Week References
λ₯Ό μ΄μ©ν΄ ν΄κ²°νλ κ²μ΄ κ°μ₯ μ’λ€.
2 ) 1κ°μ Property λ nil μ νμ©νκ³ , 1κ°μ Property λ nil μ νμ©νμ§ μλ μΌμ΄μ€
Customer μ CreditCard μμ λ 1κ°μ Property λ nil μ νμ©νκ³ , 1κ°μ Property κ° nil μ νμ©νμ§ μλ κ²½μ°μ Strong
Reference Cycles μ΄ λ°μν κ°λ₯μ±μ΄ μλ μν©μ 보μ¬μ€λ€. μ΄ μλ리μ€λ Unowned References
λ₯Ό μ΄μ©ν΄ ν΄κ²°νλ κ²μ΄ κ°μ₯ μ’λ€.
3 ) 2κ°μ Properties κ° λͺ¨λ nil μ νμ©νμ§ μλ μΌμ΄μ€
λ§μ§λ§μΌλ‘ 2κ°μ Properties κ° λͺ¨λ κ°μ΄ νμ μκ³ μ΄κΈ°νκ° μλ£λλ©΄ nil μ΄ λμ΄μλ μ λλ μΈ λ²μ§Έ μλ리μ€κ° μλ μν©μ μ€λͺ
νλ€.
μ΄ μλ리μ€λ Unowned References μ λ³νμΌλ‘ Unowned References
μ
Implicitly Unwrapped Optional Properties(Type!)
λ₯Ό μ΄μ©ν΄ ν΄κ²°νλ€.
μ΄λ κ² νλ©΄ Strong Reference Cycles λ₯Ό νΌνλ©΄μ, μ΄κΈ°νκ° μλ£λλ©΄ λ Properties λͺ¨λ Optional Unwrapping μμ΄ μ κ·Όν μ μλ€.
1 ) Unowned Optional References
Customer μ CreditCard λͺ¨λΈ κ³Ό λμΌν ννμ μΌμ΄μ€λ₯Ό λ¨Όμ νμΈνκ³ , Implicitly Unwrapped Optional Properties κ° μ μ©λ λͺ¨λΈμ νμΈν΄ λΉκ΅ν΄λ³Έλ€.
class Country {
let name: String
var capitalCity: City?
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
deinit { print("\(name) is being deinitialized") }
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
deinit { print("Card #\(number) is being deinitialized") }
}
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity!.name)")
Canada's capital city is called Ottawa
var capitalCity: City?
μinit(name:, country:)
λ₯Ό μ¬μ©νλ€.country.capitalCity!.name
μ κ°μ΄ country μ capitalCity μ μ κ·Όνλ €λ©΄ Optional Unwrapping μ΄ νμνλ€.
2 ) Unowned References and Implicitly Unwrapped Optional Properties
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
deinit { print("\(name) is being deinitialized") }
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
deinit { print("Card #\(number) is being deinitialized") }
}
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
Canada's capital city is called Ottawa
var capitalCity: City!
μinit(name:, country:)
λ₯Ό μ¬μ©νλ€.country.capitalCity.name
μ κ°μ΄ Optional Unwrapping μμ΄ country μ capitalCity μ μ κ·Όμ΄ κ°λ₯νλ€.
Country μ Initializer μ self.capitalCity = City(name: capitalName, country: self)
λ₯Ό μ΄ν΄λ³΄μ.
City μ Initializer λ Country κ° νμνλ€. νμ§λ§ Two-Phase Initialization μμ μ€λͺ
νλ―μ΄ 'self' μ°Έμ‘°λ 'Phase 2'
μμλ§ κ°λ₯
νλ€.
λ°λΌμ βselfβ μ°Έμ‘°λ₯Ό μ¬μ©νλ©΄μ, var capitalCity κ° Optional μ νμ©νμ§ μλλ‘ νκΈ° μν΄ 'City!'λ‘ νμλλ
Implicitly Unwrapped Optionals λ₯Ό μ¬μ©ν΄ nil μ ν λΉν΄ Phase 1 μ μ²λ¦¬νκ³
λ₯Ό νκ³ , Phase 2 μμ λ°λμ μ μ₯
νλ λ°©λ²μ
μ¬ μ©νλ€.
- Implicitly Unwrapped Optionals
let possibleString: String? = "An optional string." let forcedString: String = possibleString! // requires an exclamation point let assumedString: String! = "An implicitly unwrapped optional string." let implicitString: String = assumedString // no need for an exclamation point
λ§μ§λ§μΌλ‘ deallocated ν μ€νΈλ₯Ό ν΄λ³΄μ
var country: Country?
country = Country(name: "Canada", capitalName: "Ottawa")
country = nil
Country Canada is being deinitialized
City Ottawa is being deinitialized
deallocated κΉμ§ μ μμ μΌλ‘ μ²λ¦¬λλ€.
6. Strong Reference Cycles for Closures π©βπ»
μμμ λ Class Instance Properties μ¬μ΄μ
μμ±λλ Strong Reference Cycles μ μ΄λ₯Ό μ΄λ»κ² ν΄κ²°νλμ§ κ°κ°μ μλ리μ€μ λν΄
μ΄ν΄λ³΄μλ€.
μ΄λ²μλ Class Instance μ Closures μ¬μ΄μ
μμ±λλ Strong Reference Cycles μ λν΄ μμλ³Έλ€. μ΄κ²μ Class Instance
Property μ Closure λ₯Ό ν λΉνκ³ , Closure κ° 'self' λ₯Ό μ΄μ©ν΄ μμ μ΄ μν context μ Instance Properties/Methods λ₯Ό
μΊ‘μ²
ν λ μμ±λλ€.
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") }
}
μ HTMLElement class λ head
μ text
λ₯Ό λ°μ HTML μ λ§λ€μ΄μ€λ€.
var heading: HTMLElement? = HTMLElement(name: "h1")
let html = heading!.asHTML()
print(html) // <h1 />
var headingWithText: HTMLElement? = HTMLElement(name: "p", text: "Hello~")
let anotherHtml = headingWithText!.asHTML()
print(anotherHtml) // <p>Hello~</p>
asHTML
property λ String κ°μ HTML Rendering μΌλ‘ μΆλ ₯ν λλ§ νμνλ―λ‘lazy
property λ‘ μ μΈλλ€.
그리κ³lazy
λ‘ μ μΈλμμΌλ―λ‘ Property κ° νΈμΆλλ μμ μλ μ΄λ―Έ Initialization μ λ§μΉ μνλ₯Ό μλ―Ένλ€. μ¦,self
μ°Έμ‘°κ° κ°λ₯ν¨μ μλ―Ένλ€.
μ΄μ μ¬μ©μ΄ λλ¬μΌλ deallocated μμΌλ³΄μ.
heading = nil
headingWithText = nil
print(heading as Any) // nil
print(headingWithText as Any) // nil
// Nothing
λ³μ heading
κ³Ό headingWithText
μ μ°κ²°λ Strong Reference Cycles λ μ κ±°λμμ§λ§ λ Classes λͺ¨λ deallocated
λμ§ μλλ€.
- λ Classes κ° κ°λ Properties μ¬μ΄μ μμ±λ References κ° Strong Reference Cycles λ₯Ό μμ±νλ μ΄μ λ
Classes κ° Reference Types
μ΄κΈ°λλ¬Έμ΄λ€.- 그리κ³
Closures μμ Reference Types μ΄λ―λ‘, Classes μ Closures μ¬μ΄μλ Strong Reference Cycles κ° μμ±
λλ€.
- Class Instances λ context λ΄μ μ μλ Properties λλ Methods μ Pointer λ₯Ό Strong References λ‘ μ°Έμ‘°νλ€.
(μ΄ κ²½μ°asHTML
μ μμ μ Closure() -> String
μ κ°ν μ°Έμ‘°λ‘ κ°λλ€.) - Closures λ μμ μ΄ μν Context λ΄μ μ μλ Properties λλ Methods μ Pointer λ₯Ό Strong References λ‘ μ°Έμ‘°νλ€.
(μ΄ κ²½μ°() -> String
μ μμ μ΄ μν Context λ΄μ μ μλ name, text μ μ κ·ΌνκΈ° μν΄self
λ₯Ό κ°ν μ°Έμ‘°λ‘ κ°λλ€.)
Closures λ μ¬λ¬ λ² μ°Έμ‘°νμ§λ§ λ¨ νλμ
self
λ₯Ό Strong References λ‘ μΊ‘μ²νλ€.
7. Resolving Strong Reference Cycles for Closures π©βπ»
1. Defining a Capture List
Swift λ μ΄ λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ Closure Capture List
λ₯Ό μ΄μ©ν μ°μν ν΄κ²° λ°©λ²μ μ 곡νλ€. Capture List λ Closures κ°
νλ λλ κ·Έ μ΄μμ Reference Types λ₯Ό μΊ‘μ²ν λ μ¬μ©ν κ·μΉ
μ μ μνλ€. λ Classes μ κ²½μ°μ λ§μ°¬κ°μ§λ‘ Week References
λλ Unowned References
λ₯Ό μ¬μ©νλ€.
- Without Capture List
lazy var someClosure = {
(index: Int, stringToProcess: String) -> String in
// closure body goes here
}
lazy var someClosure = {
// closure body goes here
}
Closures λ Parameter List λ₯Ό context λ‘λΆν° μ μΆν μ μμ΄ μλ΅μ΄ κ°λ₯νλ€.
- With Capture List
lazy var someClosure = {
[unowned self, weak delegate = self.delegate]
(index: Int, stringToProcess: String) -> String in
// closure body goes here
}
lazy var someClosure = {
[unowned self, weak delegate = self.delegate] in
// closure body goes here
}
Parameter List λ₯Ό context λ‘λΆν° μ μΆνλλ‘ μλ΅νλλΌλ Capture List λ₯Ό μ¬μ©ν λλ
in
keyword λ₯Ό λλ½ν μ μλ€.
2. Weak and Unowned References
λ Classes μ κ²½μ°μ λ§μ°¬κ°μ§λ‘ Closures κ° μΊ‘μ²ν References κ° nil
μ΄ λ κ°λ₯μ±μ΄ μλμ§μ Lifetime μ λΉκ΅ν΄ μ¬μ©νλ€.
- Week References : μΊ‘μ²ν self κ°
nil
μ΄ λ κ°λ₯μ±μ΄ μλ κ²½μ°(Short Lifetime) μ¬μ©νλ€. μ¦, Week References λ νμOptional
μ΄λ€. - Unowned References : μΊ‘μ²ν self κ°
nil
μ΄ λ κ°λ₯μ±μ΄ μκ³ νμ μλ‘λ₯Ό μ°Έμ‘°νλ κ²½μ°(Same Lifetime) μ¬μ©νλ€. μ¦, Unowned References λForced Unwrapping
λλNon-Optional
μ΄λ€.
λ€μμ Strong Reference Cycles for Closures μ Capture List λ₯Ό μ μ©ν μ½λλ€.
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
[unowned self] in
if let text = text {
return "<\(name)>\(text)</\(name)>"
} else {
return "<\(name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit { print("\(name) is being deinitialized") }
}
var headingWithText: HTMLElement? = HTMLElement(name: "p", text: "Hello~")
headingWithText = nil
p is being deinitialized
deallocated κΉμ§ μ μμ μΌλ‘ μ²λ¦¬λλ€.
Reference
- βAutomatic Reference Counting.β The Swift Programming Language Swift 5.7. accessed Mar. 08, 2023, Swift Docs Chapter 24 - Automatic Reference Counting.