ARC
수명주기가 다한 객체의 할당을 해제하여 메모리를 관리하는 방식
- 참조 횟수를 추적하여 객체의 수명을 결정함
- Swift 컴파일러에 의해 수행됨
retain | 참조가 시작되는 시점에 삽입
release | 참조가 끝나는 시점에 삽입
컴파일 시점에 처리됨
RC가 0으로 떨어지면 객체가 메모리에서 해제됨
class Travler {
var name: String
var destingation: String?
}
func test() {
// 객체 생명주기 시작
let t1 = Traveler(name: "Lily") // Referece Start(1)
// init => RC(t1) = 1
// retain
// => RC(t1, t2) = 2
let t2 = t1 // End(1), Start(2)
// release : t1의 참조 끝
// => RC(t2) = 1
t2.destination = "Big Sur" // End(2)
// release : t2 사용 끝
// => RC = 0
print("Done")
// => 메모리 해제
RC
Swift의 기본참조는 강한 참조(Strong)
weak / unowned
- RC를 추가하지 않아 순환 참조를 방지하기 위해 주로 사용됨
순환 참조 예시
class Traveler {
var name: String
var account: Account?
func printSummary() {
if let account = account {
print("\\(name) has \\(account.points) points")
}
}
}
class Account {
var traveler: Traveler
var points: Int
}
func test() {
let traveler = Traveler(name: "Lily")
// T = 1
let account = Account(traveler: traveler, points: 1000)
// T = 2, A = 1
// -> Traveler 참조 중
traveler.account = account
// A = 2
// -> Account 참조 중
// 여기가 account에 대한 마지막 사용 시점
// => A = 1
traveler.printSummary()
// Traveler에 대한 마지막 사용 시점
// => T = 1
}
// Traveler -> Account / Account -> Traveler의 참조 카운트가 하나씩 남게됨
// Traveler가 메모리에서 해제되면 Account의 RC도 줄어서 해제 가능
위 코드는 결과적으로 각 객체들이 절대 메모리에서 해제되지 않음
이는 곧 메모리 누수로 이어짐
이러한 문제를 방지하기 위해 사용하는 것이 unowned / weak !
unowned
- 메모리 해제 후 접근 시 런타임 에러 발생
weak
- 메모리 해제 후 참조 시 nil 반환
Weak 활용 예시
class Traveler {
var name: String
var account: Account?
}
class Account {
weak var traveler: Traveler?
var points: Int
func printSummary() {
print("\\(traveler!.name) has \\(points) points")
// Force Unwrapping : 에러 발생
}
func printSummary2() {
if let traveler = traveler {
print("\\(traveler.name) has \\(points) points")
// Silent Bug : 프로그램이 원하는대로 동작하지않는데, 문제 발생 X
}
}
}
func test() {
let traveler = Traveler(name: "Lily")
// T = 1
// Traveler 더 사용되지 않음 => T = 0
let account = Account(traveler: traveler, points: 1000)
// A = 1
// T는 weak 상태로 +1 (X)
traveler.account = account
// A = 2
account.printSummary()
// Traveler == nil
// => A = 1
// printSummary() 결과가 출력이 될 수도 있는데, 이 때 출력이 되면 관찰된 객체 수명
// Travler가 참조 해제된 상태에서 접근하여 사용할 수 있게 사용 가능
withExtendedLifetime(travelre) {
account.printSummarty()
}
// defer: 함수의 가장 마지막에 실행
// - 생명주기 임의로 늘려버리기
defer { withExtendedLifetime(travelre) {} }
account.printSummary
}
withExtendedLifetime()
객체의 생명주기를 확장하고 싶을 때 사용해줄 수 있음
생명주기와 관련된 버그를 쉽게 해결할 수 있어보이지만 해당 주기에 대한 책임을 오로지 개발자가 지게 됨
weak에 대해 매번 적용해줘야 하며, 적용하지 않을 경우 버그가 일어날 가능성이 있음
그러므로 통제불능 시 해당 메서드가 반복적으로 등장해 유지보수 비용이 상승할 수 있음
'Study' 카테고리의 다른 글
📚 Concurrency Docs 내 일부 (0) | 2024.09.11 |
---|---|
🍎 WWDC21 : Explore structured concurrency in Swift (0) | 2024.09.07 |
👀 Continuation 알아보기 (0) | 2024.08.24 |
🍎 WWDC21: Meet async/await in Swift (0) | 2024.08.20 |
📚 ARC Docs 정리 (0) | 2024.08.06 |