앞선 글에서 Swift의 메모리 관리 시스템인 ARC에 대한 개념과 메모리 누수가 일어날 수 있는 상황인 강한 순환 참조에 대해서 간략하게 알아보았다. 그럼 이제 메모리 누수를 발생시키지 않을 수 있는 방법인 약한 참조 Weak Reference와 비소유참조 Unowned Reference에 대해 설명해 보겠다!
[Swift] 메모리 관리 (1) | ARC와 메모리 누수 (feat.강한순환참조)
[Swift] 메모리 관리 (2) | 약한참조 WeakReference와 비소유참조 UnownedReference
# 🥨 약한 참조
우선 약한 참조(Weak Reference)와 비소유 참조(Unowned Reference)의 공통점은 가리키는 인스턴스의 ReferenceCount(RC) 숫자를 올라가지 않게 한다는 것이다. RC 숫자를 올라가게 하지 않는다는 게 어떤 의미인지는 아래에서 자세히 알 수 있을 것이다!
앞선 글과 동일한 코드에서 var pet : Dog? 와 var owner : Person? 만 각각 weak 키워드를 붙여서 weak var pet : Dog? 와 weak var owner : Person? 로 정의해 주면 서로 가리키는 인스턴스에 대해 RC 숫자를 올라가지 않게 하기때문에 nil을 할당하면 소멸자가 실행되면서 메모리에서 해제되는 것을 확인할 수 있다.
class Person {
var name : String
var age : Int
weak var pet : Dog?
init(name : String, age : Int) {
self.name = name
self.age = age
}
deinit {
print("Person \(name) 메모리 해제")
}
}
class Dog {
var name : String
var age : Int
weak var owner : Person?
init(name : String, age : Int) {
self.name = name
self.age = age
}
deinit {
print("Dog \(name) 메모리 해제")
}
}
var person1 : Person? = Person(name: "person1", age: 20) //RC : 1
var dog1 : Dog? = Dog(name: "Dog1", age: 1) //RC : 1
person1?.pet = dog1 //RC 숫자 올라가지 않음
dog1?.owner = person1 //RC 숫자 올라가지 않음
person1 = nil
dog1 = nil
그럼 인스턴스에 nil을 할당했을 때 내부에서 순환 참조하고 있는 값들은 어떻게 될까? weak 키워드로 약한 참조를 했을 경우 인스턴스에 nil을 할당해서 참조하고 있던 인스턴스가 사라지면 자동으로 nil로 초기화된다.
(그렇기 때문에 변수를 정의할 때 옵셔널 타입으로 정의해야 한다.)
# 🥨 비소유 참조
우선 비소유 참조가 약한 참조와 가장 크게 다른 점은 항상 값이 있다는 전제하에 사용된다는 것! 즉, 인스턴스를 참조하는 도중에 인스턴스가 메모리에서 사라진 일이 없다고 가정한다는 것이다. 때문에 인스턴스를 참조하는 도중에 인스턴스가 사라지면( => 위의 약한 참조 예시처럼) 아래처럼 에러가 발생한다.
error: Execution was interrupted, reason: signal SIGABRT.
class Person {
var name : String
var age : Int
unowned var pet : Dog?
init(name : String, age : Int) {
self.name = name
self.age = age
}
deinit {
print("Person \(name) 메모리 해제")
}
}
class Dog {
var name : String
var age : Int
unowned var owner : Person?
init(name : String, age : Int) {
self.name = name
self.age = age
}
deinit {
print("Dog \(name) 메모리 해제")
}
}
var person1 : Person? = Person(name: "person1", age: 20) //RC : 1
var dog1 : Dog? = Dog(name: "Dog1", age: 1) //RC : 1
person1?.pet = dog1
dog1?.owner = person1
person1 = nil
//dog1 = nil
dog1?.owner. // ❌ error: Execution was interrupted, reason: signal SIGABRT.
반면에 먼저 이렇게 참조를 해제하면 에러가 발생하지 않는다.
dog1?.owner = nil //인스턴스 nil할당하기 전에 먼저 참조 해제
person1 = nil
dog1?.owner
이처럼 약한 참조와의 차이점은 참조하고 있는 인스턴스에 nil이 할당되었다고 해도 약한 참조처럼 ARC에 의해 자동으로 nil로 초기화되지 않는다는 것이다. 원래는 옵셔널 값으로 타입을 정의하는 것도 안되었지만 현재는 옵셔널 타입 선언도 가능해졌다.
이러한 이유들로 소유자보다 인스턴스의 생명 주기가 더 길 경우에 사용되며 일반적으로는 비소유 참조보다 약한참조을 주로 사용한다.
# 🥨 공통점과 차이점을 정리하자면!
공통점
- 가리키는 인스턴스의 Reference Counting(RC) 숫자를 올라가지 않게 한다.
차이점
- 약한 참조 Weak Reference
- 참조하는 인스턴스가 메모리에서 해제되면 자동으로 소유자는 nil로 초기화된다.
- 따라서, 상수 let이 아닌 변수 var로 선언해야 하고 옵셔널 타입으로 선언되어야 한다!
- 비소유 참조 Unowned Reference
- 참조하는 인스턴스가 메모리에서 해제된다고 해도 자동으로 소유자를 nil로 초기화시키지 않는다.
- 소유자보다 인스턴스의 생명주기가 더 긴 경우에 사용한다.
이렇게 swift에서 메모리 누수가 발생하지 않게 할 수 있는 방법에 대해 알아보았다. 약한 참조(Weak Reference) 와 비소유 참조(Unowned Reference) 두 가지 방법이 있지만 보통은 약한 참조만 사용해도 문제가 없기 때문에 비소유참조는 되도록 사용하지 않는 것이 나을 것 같다.
'Swift' 카테고리의 다른 글
[Swift] 클로저와 중첩함수의 캡처 현상 (2) | 캡처리스트 클로저에서self키워드 (5) | 2023.11.09 |
---|---|
[Swift] 클로저와 중첩함수의 캡처 현상 (1) (47) | 2023.11.01 |
[Swift] 메모리 관리 (1) | ARC와 메모리 누수 (feat.강한순환참조) (1) | 2023.10.16 |
[Swift] Initialization (2) | 상속, 확장에서의 생성자 (0) | 2023.10.10 |
[Swift] Initialization (1) | 생성자의 종류 : 지정생성자, 편의생성자, 필수생성자, 실패가능생성자 (0) | 2023.10.04 |