ARC와 메모리 누수
메모리를 관리해주는 ARC와 메모리 누수를
해결하는 방법에 대한 내용을 정리해봤습니다.
ARC(Automatic Reference Counting)
Swift의 ARC(Automatic Reference Counting)는 메모리 관리 기법 중 하나로,
객체나 인스턴스가 참조되는 횟수를 추적하여 메모리에서 해제할 시점을 결정하고
객체가 생성될 때마다 참조 횟수가 1 증가하고, 해당 객체를 참조하는 다른 객체나 변수가 없어지거나
더 이상 사용되지 않을 때 참조 횟수가 1 감소.
참조 횟수가 0이 되면 해당 객체는 메모리에서 해제
ARC의 작동 방식
1 2 3 4 객체 생성 객체 참조 참조 해제 Zeroing Weak References 객체가 생성되면
참조 횟수가 1 증가객체를 다른 변수나 상수에
할당하면 해당 객체의
참조 횟수가 1 증가객체의 참조가 없어지면
(참조하는 변수나 상수가
없거나 **nil**이 할당되면) 참조 횟수가 1 감소약한 참조
(Weak Reference)는
객체의 참조 횟수를
증가시키지 않고 추적,
객체가 해제되면 약한 참조는 자동으로 **nil**로 설정
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit { // deinit은 Person 인스턴스가 메모리에서 해제될 때 그 때 직전에 불리게 되는 함수입니다.
// 즉, deinit은 인스턴스가 메모리에서 해제될 때 사용하는
print("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1 = Person(name: "Jun") // RC: 1
/*
Person의 이름이 Jun 이라는 이름의 인스턴스를 생성하고
reference1에 담게 되면, 레퍼런스 카운팅이 1이 증가합니다. (객체를 생성 하였기 때문입니다.)
다른변수에 참조를 할당 하는 경우 레퍼런스 카운터가 하나 더 증가 됩니다.
즉, Person으로 만들어진 인스턴스는 reference1도 Person을 바라 보고 있으며,
reference2와 3도 Person을 바라보고 있는 것 입니다.
이때 reference1에 nil을 할당 하면 그 순간 레퍼런스 카운팅이 -1이 적용이 되어 1이 감소합니다.
레퍼런스가 1인 상태에서는 메모리가 해지될 수 없습니다.
*/
// Prints "John Appleseed is being initialized"
reference2 = reference1 // RC: 2
reference3 = reference1 // RC: 3
reference1 = nil // RC: 2
reference2 = nil // RC: 1
reference3 = nil // RC: 0
// Prints "Jun is being deinitialized"
강한 참조 순환(Strong Reference Cycle)문제와 해결 방법
Swift로 개발할 때에는 메모리 누수(Memory Leak)을 주의!
참조는 디폴트로 강한 참조(Strong Reference)를 사용하는데,
이 강한 참조를 잘못 사용하면 메모리 누수(Memory Leak) 문제가 발생
[ 가장 대표적인 예 ]
두 개 이상의 인스턴스가 서로가 서로를 강한 참조일 때 발생
이 문제를 강한 참조 순환(Strong Reference Cycle or Retain Cycle)이라고 부릅니다.
강한 참조 순환 : 메모리가 해제되지 않고 유지되어 메모리 누수가 발생하는 현상
// 강한 참조 순환 문제의 예시
class Man {
var name: String
var girlfriend: Woman? // 옵셔널
init(name: String) {
self.name = name
}
deinit { print("Man Deinit!") } // 인스턴스가 해제되기 직전에 불림
}
class Woman {
var name: String
var boyfriend: Man? // 옵셔널
init(name: String) {
self.name = name
}
deinit { print("Woman Deinit!") } // 인스턴스가 해제되기 직전에 불림
}
/*
아래 코드에서는 인스턴스를 생성하고 있습니다.
옵셔널 Man 타입으로 철수라는 사람을 만들고 있으며,
옵셔널 Woman 타입으로 영희 라는 사람을 만들고 있습니다.
그리고, 철수의 여자친구로 영희를 넣어주고 있고 영희의 남자친구로 철수를 넣어주고 있습니다.
이 상태에서 둘의 인스턴스의 메모리를 해제하고자 합니다.
즉, 영희와 철수를 프로그램상에서 사용 하지 않으려고 하는 것입니다.
그래서 철수와 영희에게 각각 nil 값을 넣어주었는데... 두 객체는 해제되지 않습니다.
그 이유는 객체를 생성할 때 레퍼런스 카운터가 1씩 증가하는데 그 참조값을 철수와 영희는 가지고 있습니다.
이 때 다른 변수에 재할당이 되었을 때 레퍼런스 카운터가 하나씩 증가해서 아래의 두개의 인스턴스는 각각의
레퍼런스 카운터가 2씩 됩니다. 그렇기 때문에 각각의 인스턴스의 nil을 넣는다고 해도 1개가 줄기 때문에
1이 남아있는 상태가 되어 deinit을 호출하지 못하는 것입니다.
*/
// 인스턴스 생성
var chelosu: Man? = .init(name: "철수") // RC 1
var yeonghee: Woman? = .init(name: "영희") // RC 1
chelosu?.girlfriend = yeonghee // 재할당 -> RC 2
yeonghee?.boyfriend = chelosu // 재할당 -> RC 2
chelosu = nil
yeonghee = nil
강한 참조 순환 문제 해결 방법
약한 참조(Weak Reference)
- 참조되는 대상을 약하게 참조하여 순환 참조를 방지
- 참조하는 객체를 강제로 유지하지 않고, 참조 대상이 메모리에서 해제되면 자동으로 **nil**로 설정
- 두 객체가 서로를 강하게 참조하는 경우, 순환 참조로 인해 메모리 누수가 발생가능
이런 상황에서는 한쪽을 **weak**로 선언하여 순환 참조 문제를 해결
- 주로 순환 참조를 방지하기 위해 사용
**weak**
옵셔널로 선언되는 참조
비소유 참조(Unowned Reference)
참조되는 대상이 항상 유효한 경우에만 사용하며, 해당 대상이 해제될 수 있는 상황에는 사용금지
**unowned**
옵셔널이 아닌 비소유 참조
항상 값이 있다고 가정, 참조하는 객체가 해제되면 런타임 에러가 발생가능
unowned
- 참조는 참조 대상이 해제될 수 있는 경우에만 사용
해당하는 객체가 메모리에서 해제되지 않은 상태에서만 해당 unowned 참조를 사용
// 약한 참조(Weak Reference)를 사용한 해결 방법
/*
Man이나 Woman 두쪽중 한쪽에만 weak를 사용해주면 됩니다.
weak를 써주는 동안 레퍼런스카운터를 올리지 않습니다.
*/
class Man {
var name: String
weak var girlfriend: Woman?
init(name: String) {
self.name = name
}
deinit { print("Man Deinit!") }
}
class Woman {
var name: String
var boyfriend: Man?
init(name: String) {
self.name = name
}
deinit { print("Woman Deinit!") }
}
var chelosu: Man? = .init(name: "철수")
var yeonghee: Woman? = .init(name: "영희")
chelosu?.girlfriend = yeonghee
yeonghee?.boyfriend = chelosu
chelosu = nil
yeonghee = nil
chelosu?.girlfriend // nil
// 비소유 참조(Unowned Reference)를 사용하여 강한 참조 순환 문제를 해결
// unowned 사용
class Man {
var name: String
unowned var girlfriend: Woman?
init(name: String) {
self.name = name
}
deinit { print("Man Deinit!") }
}
class Woman {
var name: String
var boyfriend: Man?
init(name: String) {
self.name = name
}
deinit { print("Woman Deinit!") }
}
var chelosu: Man? = .init(name: "철수")
var yeonghee: Woman? = .init(name: "영희")
chelosu?.girlfriend = yeonghee
yeonghee?.boyfriend = chelosu
yeonghee = nil
chelosu?.girlfriend // 에러 발생
'IOS > Swift-Study' 카테고리의 다른 글
[Swift-Study] 심화 문법종합반 2주차 4일차 정리 - 확장 (0) | 2024.03.27 |
---|---|
[Swift-Study] 심화 문법종합반 2주차 4일차 정리 - 프로토콜 (0) | 2024.03.27 |
[Swift-Study] 심화 문법종합반 2주차 3일차 정리 - 예외처리 (0) | 2024.03.25 |
[Swift-Study] 심화 문법종합반 2주차 3일차 정리 - 고차함수 (0) | 2024.03.25 |
[Swift-Study] 심화 문법종합반 2주차 2일차 정리 - 클로저 (0) | 2024.03.22 |