-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
강한 참조 순환과 메모리 누수
🌀 강한 참조 순환이란?
강한 참조 순환은 두 개 이상의 인스턴스가 서로를 강한 참조로 가리키고 있어서, 참조 횟수가 0이 되지 않는 상황을 말함.
이런 상황이 발생하면 ARC가 메모리에서 해제할 수 없게 되고, 메모리 누수(Memory Leak)가 발생하게 된다... 😱
🏘️ 강한 참조 순환 예시
class Member {
let name: String
var registeredGym: Gym?
init(name: String) {
self.name = name
}
deinit {
print("\(name) Member deinitialized")
}
}
class Gym {
let name: String
var registeredMember: Member?
init(name: String) {
self.name = name
}
deinit {
print("\(name) Gym deinitialized")
}
}
// 인스턴스 생성
var gilbong: Member? = Member(name: "Gilbong")
var muscleMania: Gym? = Gym(name: "MuscleMania")
// 강한 참조 순환 발생
gilbong?.registeredGym = muscleMania
muscleMania?.registeredMember = gilbong
// 강한 참조 순환으로 인해 메모리 해제되지 않음
gilbong = nil
muscleMania = nil위 코드에서 Member 인스턴스 gilbong과 Gym 인스턴스 muscleMania가 서로를 강한 참조로 가리키고 있음.
그래서 gilbong과 muscleMania를 nil로 설정해도 참조 횟수가 0이 되지 않아 메모리에서 해제되지 않는다. 메모리 누수가 발생하는 것!
💪 강한 참조 순환 해결하기
강한 참조 순환을 해결하려면 강한 참조 중 하나를 약한 참조나 미소유 참조로 바꿔주면 된다.
- 약한 참조(Weak Reference): 참조 횟수를 증가시키지 않는 참조. 참조하던 인스턴스가 메모리에서 해제되면 자동으로
nil이 됨. - 미소유 참조(Unowned Reference): 참조 횟수를 증가시키지 않지만, 참조하던 인스턴스가 메모리에서 해제되는 시점을 관리하지 않는다.
🌿 약한 참조 사용하기
class Member {
let name: String
var registeredGym: Gym?
init(name: String) {
self.name = name
}
deinit {
print("\(name) Member deinitialized")
}
}
class Gym {
let name: String
weak var registeredMember: Member?
init(name: String) {
self.name = name
}
deinit {
print("\(name) Gym deinitialized")
}
}Gym 클래스의 registeredMember 프로퍼티를 weak 키워드를 사용해 약한 참조로 선언.
이제 gilbong이 nil이 되면 Member 인스턴스는 메모리에서 해제되고, muscleMania의 registeredMember 프로퍼티도 nil이 된다.
😎 미소유 참조 사용하기
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit {
print("\(name) 메모리에서 해제")
}
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit {
print("\(number) 카드 메모리에서 해제")
}
}CreditCard 클래스의 customer 프로퍼티를 unowned 키워드를 사용해 미소유 참조로 선언.
미소유 참조는 참조 횟수를 증가시키지 않지만,
참조하던 인스턴스가 메모리에서 해제되는 시점을 관리하지 않기 때문에 주의해서 사용해야 한다
🔄 클로저에서의 강한 참조 순환
class ExerciseMachine {
let name: String
let description: String?
lazy var instructions: () -> String = { [unowned self] in
if let description = self.description {
return "[\(self.name)] \(description) - 이렇게 사용하세요!"
} else {
return "[\(self.name)] 설명 없음"
}
}
init(name: String, description: String? = nil) {
self.name = name
self.description = description
}
deinit {
print("\(name) 기구 메모리에서 해제")
}
}
let treadmill = ExerciseMachine(name: "런닝머신", description: "달리는 운동기구")
print(treadmill.instructions())// "[런닝머신] 달리는 운동기구 - 이렇게 사용하세요!" 출력위 코드에서는 클로저의 캡처 리스트에 [unowned self]를 사용해 self를 미소유 참조로 캡처.
이렇게 하면 클로저가 self를 강한 참조로 가리키지 않기 때문에 강한 참조 순환이 발생하지 않는다!
🏁 정리하기
- 강한 참조 순환은 인스턴스들이 서로를 강한 참조로 가리키고 있어 메모리 누수가 발생하는 상황.
- 강한 참조 중 하나를 약한 참조나 미소유 참조로 바꿔주면 강한 참조 순환을 해결할 수 있음.
- 약한 참조는 참조하던 인스턴스가 메모리에서 해제되면 자동으로
nil. - 미소유 참조는 참조하던 인스턴스가 메모리에서 해제되는 시점을 관리하지 않는다
- 약한 참조는 참조하던 인스턴스가 메모리에서 해제되면 자동으로
- 클로저에서
self를 캡처해 사용할 때도 강한 참조 순환이 발생할 수 있다.- 캡처 리스트에서
self를 약한 참조나 미소유 참조로 지정해 주면 된다.
- 캡처 리스트에서
Metadata
Metadata
Assignees
Labels
No labels