스위프트는 ARC를 사용하여 앱의 메모리 사용량을 추적하고 관리한다. ARC는 해당 인스턴스가 더 이상 필요하지 않을 때 클래스 인스턴스에서 사용하는 메모리를 자동으로 해제한다. 클래스 인스턴스가 더이상 필요하지 않을때 해당 메모리를 자동으로 비워준다. 참조 될때마다 참조횟수가 +1이 되고 nil을 할당해주면 참조 횟수가 -1이 된다.
ARC의 메모리 수거 대상으로는 참조타입인 클래스에만 적용되고 값 타입인 구조체나 열거형 등은 수거 대상에서 제외된다.
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var p1: Person? = Person(name: SJ)
var p2: Person? = p1
var p3: Person? = p2
p1 = nil
p2 = nil
init은 초기화 함수이고 deinit함수는 클래스 인스턴스의 메모리가 해제되었을 때 호출되는 함수이다. p1이라는 인스턴스 변수를 생성하여 초기화 하고, p2 변수에 p1을 할당, p3에 p2를 할당했다. 즉 p1과 p2 p3에 Person 인스턴스가 할당된 것(강한참조==Strong)이다. 그러면 Reference count는 각각 1이기 때문에 ARC는 이 메모리를 해제하지 않는다.
이후 실험을 위해 p1과 p2에 nil을 할당하였다. 결과는? ...
생성만 되었다.🤔 이유는 아직 p3에서 참조하고 있기 때문!!
p3 = nil
위 코드에 p3에 nil을 할당하는 것을 추가하고 다시 실행해보았다.
결과는 드디어 소멸되었다.
결론은 ARC는 인스턴스에 대한 강한참조가 남아있으면 절대 메모리에서 해제하지 않는다❗️
즉 이말은 안좋게 보면 메모리에서 해제되지 않는다는 건 메모리 누수로 이어질 수 있다는 것이다...👎🏻Shit...
내가 iOS 앱 개발을 공부하면서 Weak와 Strong 키워드를 보게 된 것은 텍스트 필드나 라벨 테이블 뷰 등을 아울렛 변수로 연결할 때이다.
@IBOutlet weak var tableView: UITableView!
처음엔 이게 뭔가 별로 중요하지 않겠구나 하고 넘어갔는데 메모리 참조와 관련된 면접에서도 단골로 나오는 내용이었다.
우선 강한참조(Strong)란 Reference count를 증가시키는 것이다. 이 키워드를 사용하는 경우는 위에 ARC의 예시코드에서 보았던
var p1 = Person(name: "sj")
클래스의 인스턴스를 할당하는 경우인데 이를 통해 ARC의 메모리 해제를 피하고, 객체를 안전하게 사용하고자 할 때 사용❗️한다.
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")
unit4A = Apartment(unit: "4A")
// 여기 주의 깊게 볼것❗️
john!.apartment = unit4A
unit4A!.tenant = john
다음 코드의 참조관계는 우선 Person 인스턴스를 할당받은 john변수는 Person 인스턴스를 강하게 참조하고 Apartment 인스턴스를 할당받은 unit4A또한 Apartment 인스턴스를 강하게 참조한다.
주의 깊게 봐야 할 것은 Person 클래스 안에 있는 apartment 프로퍼티의 타입이 Apartment타입이라는 점과 Apartment 클래스 안에 있는 tenant이 Person타입이라는 점이다. 집이 있으면 거주자가 있기 때문에 서로 강하게 참조한다.
john = nil
unit4A = nil
이렇게 john과 unit4A에 nil을 할당해도 프로퍼티의 강한 참조로 인해 reference count는 0이 되지 않고 1인 상태이다.
이를 해결하기 위한 2가지 방법이 있다. 바로 Weak(약한 참조)와 Unowned(소유 되지 않은 참조)이다.
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❗️
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
위에서의 코드와는 Apartment클래스의 프로퍼티 tenant 앞에 weak를 붙인 것 밖에 차이가 없다. 참조관계는 아래와 같아진다.
한쪽은 강하게 한쪽은 약하게 참조하고 있는 상태에서 john에 nil을 할당하는 순간
john = nil
// Prints "John Appleseed is being deinitialized"
다음과 같이 unit4A만 강하게 참조하고 나머지 참조관계는 없어지게 된다. 여기서 중요한 것은 강한 참조 일때와 달리 약한 참조를 한 객체는 ARC에서 자동으로 객체의 메모리까지 해제를 시켜준다는 점이다. 즉 원래라면 unit4A!.tenant = nil을 통해 완전히 해제해야 하지만 weak 키워드가 붙어서 자동으로 해제되었다는 것이다.👍🏻
메모리 누수를 막을 수 있는 또 다른 방법은 Unowned(소유 되지 않은 참조)이다. 그러나 Weak와 다른 점은 다른 인스턴스와 수명이 같거나 더 긴 경우에 사용된다. 또한 약한 참조와 달리 소유되지 않은 참조에는 항상 값이 있어야 한다. 즉 Optional을 사용할 수 없다는 것이다. 앞에서는 nil을 할당하여 ARC로부터 메모리를 해제 당할 수 있었지만 미소유 참조에서는 그 참조값이 계속 유지된다는 것이다.
사용법은 Weak와 같이 Unowned키워드를 붙여주기만 하면된다.
Strong, Weak, Unowned에 대해서 알아보았다. 정리해보면 상황에 맞게 이 키워드들을 사용해야 하는것이다. 아직 엄청 큰 프로젝트를 안해봤지만 분명 큰 프로젝트를 하게 되고 이런 내용을 모르면 메모리의 심각한 누수를 겪게 될 것이기에 더 공부해야겠다...📚