Swift

[WWDC] ARC in Swift

양밀루 2025. 5. 30. 20:29

✅ ARC란?

Swift에서 메모리 관리를 자동으로 수행하는 방법

 

 객체의 생명주기

  • 객체의 수명은 init 시점에 시작되어 마지막 사용 시점에 종료됩니다
  • ARC는 객체의 수명이 종료된 후 해당 객체를 해제합니다
  • ARC는 참조 카운트를 통해 객체의 수명을 추적합니다
  • Swift 컴파일러는 retain/release 작업을 삽입합니다
  • Swift 런타임은 참조 카운트가 0인 객체를 해제합니다

 

 ARC의 동작방식

class Traveler {
    var name: String = ""
    var destination: String?
}

func test() {
    let traveler1 = Traveler(name: "Lily")
    // retain
    let traveler2 = traveler1 // REFERENCE BEGINS
    // release
    traveler2.destination = "Big Sur" // REFERENCE ENDS
    // release
    print("Done traveling")
}
  • ARC는 클래스 인스턴스를 기준으로 동작
  • 객체가 생성되면 참조 카운트가 1로 설정
  • 다른 변수나 상수가 해당 객체를 참조하면 카운트가 증가하고, 참조가 해제되면 카운트가 감소
  • 참조 카운트가 0이 되면, ARC는 해당 객체를 메모리에서 해제

Swift에서 객체의 생명 주기는 괄호가 닫힐 때까지 보장되는 C++과 달리 실제 사용 여부에 따라 결정되고, ARC의 최적화에 따라 정해지기 때문에 정확한 시점을 알순 없음

 

 

 순환 참조

순환참조

Traveler와 Account가 서로 참조하고 있음

두 객체의 사용이 끝난 시점에도 서로를 참조하고 있던 참조 카운트 1이 남아있어 메모리가 해지되지 못하고 Memory Leak 발생

 

 

 Weak과 Unowned

  weak unowned
ARC 카운트 증가 ❌ 증가 안 함 ❌ 증가 안 함
nil 허용 여부 ✅ Optional Non-Optional
사용 시기 참조 대상이 먼저 해제될 수 있을 때 (ex. delegate) 참조 대상이 자신보다 오래 살아있을 것이 확실할 때

 

 

class Traveler {
    var name: String
    var account: Account?
}

class Account {
    weak var traveler: Traveler?
    var points: Int
    func printSummary() {
         if let traveler = traveler {
            print("\(traveler.name) has \(points) points") 
         }
    }
}

func test() {
    let traveler = Traveler(name: "Lily")
    let account = Account(traveler: traveler, points: 1000)
    traveler.account = account // 이때 객체가 해제된다면,
    account.printSummary() // 이미 해제된 객체에 접근하려고 할수 있음
}

 

 

weak 참조는 참조 카운트를 증가시키지 않기 때문에 순환 참조를 방지
weak으로 선언된 프로퍼티는 항상 Optional(참조하는 객체가 nil일수 있으므로)이어야 하는데, 여기서 문제가 발생할 수도 있음!!

 

 

 

✅ 강한 순환 참조 해결 방법

1) withExtendedLifetime()

func test() {
    let traveler = Traveler(name: "Lily")
    let account = Account(traveler: traveler, points: 1000)
    traveler.account = account
    withExtendedLifetime(traveler) {
        account.printSummary()
    }
}

func test() {
    let traveler = Traveler(name: "Lily")
    let account = Account(traveler: traveler, points: 1000)
    traveler.account = account
    account.printSummary()
    withExtendedLifetime(traveler) {}
}

func test() {
    let traveler = Traveler(name: "Lily")
    let account = Account(traveler: traveler, points: 1000)
    defer {withExtendedLifetime(traveler) {}}
    traveler.account = account
    account.printSummary()
}

withExtendedLifetime()을 이용해서 명시적으로 생명주기를 연장할 수 있음

하지만, 잠재적으로 버그 가능성이 있는 곳마다 매번 이 메서드를 사용하는건 피곤하고 가독성도 좋지 않음

 

2) 재설계하기

위 예시 코드에서 Account 클래스는 단지 Traveler 의 이름만 필요한 것이기 때문에, PersonalInfo 라는 타입을 새로 만들고 여기서 name을 갖게 하면 근본적으로 순환 참조를 해결할 수 있음!!!

 

 

 

 

https://developer.apple.com/kr/videos/play/wwdc2021/10216/

 

ARC in Swift: Basics and beyond - WWDC21 - 비디오 - Apple Developer

Learn about the basics of object lifetimes and ARC in Swift. Dive deep into what language features make object lifetimes observable,...

developer.apple.com