ARC는 클래스의 인스턴스 참조 카운팅을 자동으로 해줌으로서 메모리 관리를 쉽게 해주는 방식이다. 예전에 Objective-C에서 사용하던 MRC(Menual Reference Counting)는 참조 카운팅을 하는 코딩을 일일히 작성해야 했기에 코드도 길어지고 불편했지만 ARC로 인해 사용자는 메모리 관리에 대해 거의 신경쓰지 않을 수 있게 되었다.
클래스의 인스턴스를 생성할 때 마다 ARC는 그 인스턴스와 관련된 정보들을 Heap 영역 메모리에 할당하고 인스턴스가 필요 없어지면 제거하여 다시 메모리 공간을 확보한다. 그런데 ARC가 인스턴스의 프로퍼티나 함수가 다른 곳에서 사용되고 있는 상태에서 인스턴스를 제거하고 프로퍼티나 함수에 접근하려 한다면 문제가 생길 것이다. 그래서 ARC는 인스턴스가 변수나 상수 혹은 프로퍼티에 할당될 때 강한 참조(Strong Reference)를 기본적으로 생성하고 참조 카운트가 0이 되지 않는 이상 인스턴스를 메모리에서 제거하지 않는다.
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
// count = 2
reference3 = reference1
// count = 3
reference1 = nil
// count = 2
reference2 = nil
// count = 1
reference3 = nil
// count = 0
// Prints "John Appleseed is being deinitialized"
위와 같이 reference1에서 Person 클래스를 초기화했지만 reference1의 값이 nil이 돼도 reference2와 reference3가 같은 인스턴스를 참조하고 있기 때문에 제거되지 않았고 참조하고 있는 모든 변수가 nil이 되어 참조 카운트가 0이 되었을 때에서야 메모리에서 제거된다.
그런데 위와 달리 참조 카운트가 0이 되지 않는 경우가 있다. 두 클래스의 인스턴스가 각각 서로를 참조할 때 강한 참조 사이클(Strong Reference Cycle)이 발생한다.

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") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed") // Person instance reference count: 1
unit4A = Apartment(unit: "4A") // Apartment instance reference count: 1

john!.apartment = unit4A // count: 2
unit4A!.tenant = john // count: 2

john = nil // count: 1
unit4A = nil // count: 1
john과 unit4A의 값이 nil이되었지만 Person instance와 Apartment instance는 서로를 참조하고 있기 때문에 메모리에서 제거되지 않았다. 이렇게 되면 각각의 인스턴스에 접근할 수도 없고 제거할 수도 없는 메모리 누수가 발생한다. 이를 해결하는 방법은 weak이나 unowned reference를 이용하는 것이다.
첫번째는 클래스 프로퍼티를 weak var로 선언하는 방법이다. 두 개의 클래스가 상호 참조하는 상황에서 생명주기가 더 긴 클래스의 프로퍼티에 주로 사용한다. 이렇게 하면 ARC가 인스턴스의 참조카운트가 0이 아니더라도 강한 참조가 하나도 남아있지 않다면 약한 참조를 nil로 만들고 인스턴스를 제거한다. 그래서 약한 참조는 항상 옵셔널 타입의 변수여야 한다.

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

john = nil
// Prints "John Appleseed is being deinitialized"
두번째는 클래스의 프로퍼티나 변수에 unowned 키워드를 붙여 선언하는 방법이다. 이 경우 두 개의 클래스가 상호 참조하는 상황에서 생명주기가 같거나 더 짧은 클래스의 프로퍼티에 주로 사용한다. 약한 참조와는 다르게 미소유 참조(unowned reference)는 항상 값이 있을거라고 예상될 때 사용된다.

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!)
john = nil
// Prints "John Appleseed is being deinitialized"
// Prints "Card #1234567890123456 is being deinitialized"
unowned reference도 weak처럼 옵셔널로 사용이 가능하다. weak과의 차이점은 항상 유효한 객체를 참조하거나 nil로 설정되어 있는지 확인해야 한다는 점이다.

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]
학과(Department) 클래스는 각 강의(Course)에 대해 강한 참조를 가지고 있고 한 과목에는 부서와 다음 강의에 대한 참조만 가지고 있고 소유하고 있지는 않다. 모든 강의는 학과안에서 이루어지므로 강의 클래스 내에 학과 프로퍼티는 옵셔널이 아니다. 하지만 다음 강의는 없을 수도 있으므로 옵셔널이다.
학과 인스턴스에는 초급(intro),중급(intermediate),고급(advanced) 총 3개의 강의 배열이 있으며 초급의 다음 강의 프로퍼티는 중급을 참조하고 중급의 다음 강의 프로퍼티는 고급을 참조하지만 고급은 다음 강의가 없으므로 nil이다.
unowned optional reference도 마찬가지로 인스턴스에 대한 강한 참조가 존재하지 않을 때 인스턴스가 할당이 해제되는 것을 막지 않는다. unowned reference와 똑같이 동작하고 다른 점은 값이 nil이 될 수도 있다는 점이다. 또한 클래스를 참조하는 옵셔널 프로퍼티는 참조 카운팅을 사용하지 않는다.
이전의 예제에서 두 클래스의 인스턴스가 각각 서로를 참조해서 강한 참조 사이클이 생기는 경우 양쪽이 nil이 될 가능성이 있을 때 weak reference를 사용했고(Person and Apartment) 한 쪽만 nil이 될 가능성이 있는 경우에는 unowned reference를 사용했다.(Customer and CreditCard)
만약 양쪽 모두 항상 값을 가지고 있고 nil이 되지 않아야 할 때는 unowned refence와 Implicitly Unwrapped Optional Property를 함께 사용하면 된다. 이렇게 하면 프로퍼티를 언래핑 하지 않고도 사용할 수도 있고 강한 참조 사이클도 피할 수 있다.
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// Prints "Canada's capital city is called Ottawa"
클래스 인스턴스의 프로퍼티에 클로저를 할당할때 강한 참조 사이클이 발생할 수 있다. 클로저 안에서 클로저가 속해있는 클래스의 프로퍼티나 메소드에 접근할 때는 self 키워드를 붙여서 사용하기 때문에 클로저가 인스턴스를 강하게 참조한다.(클로저도 클래스처럼 참조 타입) 인스턴스의 프로퍼티로 클로저 할당했을때 강한 참조가 생기기 때문에 서로를 강하게 참조하면서 강한 참조 사이클이 발생한다.

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")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"
paragraph = nil
// the message in the HTMLElement deinitializer isn’t printed
클로저 캡처 리스트(Closure Capture List)에 weak이나 unowned reference를 사용해서 해결이 가능하다. 클로저 캡처 리스트는 다음과 같이 클로저의 파라미터와 리턴값을 작성하기 전에 대괄호를 사용해서 작성할 수 있다.(파라미터나 리턴값이 없으면 in키워드 앞에 작성하면 된다.)
lazy var someClosure = {
[unowned self, weak delegate = self.delegate]
(index: Int, stringToProcess: String) -> String in
// closure body goes here
클로저 캡처 리스트 안에 [weak self]를 써야할지 [unowned self]를 써야할지는 클로저와 인스턴스가 항상 서로를 참조하고 동시에 할당이 해제될 때 [unowned self]를 사용하고 캡처 대상이 nil이 될 가능성이 있는 경우에 [weak self]를 사용한다.

class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
[unowned self] in
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")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"
paragraph = nil
// Prints "p is being deinitialized"