Swift: ARC

틀틀보·2025년 5월 13일

Swift

목록 보기
6/19

객체의 생명 주기

  1. Live: 객체가 활성 상태이며, 참조 카운트가 초기화된 상태

  2. Deiniting: 강한 참조 카운트가 0이 되어 deinit()이 호출되는 상태

  3. Deinited: deinit()이 완료되었지만, 아직 unowned 참조가 존재하는 상태

  4. Freed: 객체의 메모리가 해제되었지만, Side table이 아직 존재하는 상태

  5. Dead: 모든 참조가 사라져 객체와 Side table이 완전히 해제된 상태

ARC (Automatic Reference Counting)

클래스 인스턴스의 메모리를 자동으로 관리해주는 시스템
인스턴스의 참조 횟수 추적을 통한 관리

  • 클래스 인스턴스가 생성되면 해당 인스턴스의 참조 횟수(RC)를 1로 설정
  • 생성된 인스턴스를 다른 변수, 상수에 할당하면 참조 횟수는 증가
  • 할당 해제되면 참조 횟수 감소
  • 참조 횟수가 0이 되면 ARC가 해당 인스턴스를 메모리에서 해제
class Car {
    let model: String
    let manufacturer: String

    init(model: String, manufacturer: String) {
        self.model = model
        self.manufacturer = manufacturer
    }

    deinit {
        print("The Car is about to be deallocated.")
    }
}

var firstCar: Car? = Car(model: "EV6", manufacturer: "KIA")
var secondCar: Car? = firstCar

firstCar = nil
secondCar = nil
  • 생성한 인스턴스를 할당한 변수 firstCar에 의해 참조 횟수 +1 (1)
  • firstCar를 참조하는 secondCar 변수에 의해 참조 횟수 +1 (2)
  • firstCar에 할당된 인스턴스를 nil로 교체 참조 횟수 -1 (1)
  • secondCar에 할당된 인스턴스를 nil로 교체 참조 횟수 -1 (0)
  • 참조 횟수가 0이 된 Car 객체를 ARC가 메모리에서 해제함.

순환 참조 문제

두 개 이상의 클래스 인스턴스가 서로를 강하게 참조하여 ARC가 이들을 메모리에서 해제하지 못하는 문제

class Person {
    var dog: Dog?
}

class Dog {
    var owner: Person?
}

var john: Person? = Person()
var fido: Dog? = Dog()

john?.dog = fido
fido?.owner = john

john = nil
fido = nil
  • Person, Dog 객체를 변수에 할당
    • Person +1 (1), Dog +1 (1)
  • Person 객체의 프로퍼티 dogDog 객체를 참조
    • Dog +1 (2)
  • Dog 객체의 프로퍼티 ownerPerson 객체를 참조
    • Person +1 (2)
  • john 변수에 할당된 Person 객체를 해제
    • Person -1 (1)
  • fido 변수에 할당된 Dog 객체를 해제
    • Dog -1 (1)
  • 여전히 Person, Dog 객체의 참조 횟수가 0이 아니라서 ARC가 메모리에서 해제시키지 못하는 문제

weak와 unowned

weak

약한 참조로 참조 대상이 해제되면 자동으로 nil
참조 횟수를 증가 시키지 X

class Person {
    weak var dog: Dog?
}

class Dog {
    weak var owner: Person?
}

var john: Person? = Person()
var fido: Dog? = Dog()

john?.dog = fido
fido?.owner = john

john = nil
fido = nil
  • Person, Dog 객체를 변수에 할당
    • Person +1 (1), Dog +1 (1)
  • Person 객체의 프로퍼티 dogDog 객체를 약한 참조 (참조 횟수를 증가 시키지 않음)
    • Dog +0 (1)
  • Dog 객체의 프로퍼티 ownerPerson 객체를 약한 참조 (참조 횟수를 증가 시키지 않음)
    • Person +0 (1)
  • john 변수에 할당된 Person 객체를 해제
    • Person -1 (0)
  • fido 변수에 할당된 Dog 객체를 해제
    • Dog -1 (0)
  • 참조 횟수가 0이 된 두 객체를 ARC가 메모리에서 해제

⚠️ weak 키워드로 선언된 변수, 상수는 참조하던 객체가 nil이 되면 nil이 되어야 하기 때문에 Optional로 선언되어야 한다.

unowned

비소유 참조로 참조 대상이 해제되더라도 nil이 되지 않음.
참조 대상이 항상 존재한다고 확신할 수 있는 경우에 사용

class Person {
    var dog: Dog?
}

class Dog {
    unowned var owner: Person
}

var john: Person? = Person()
var fido: Dog? = Dog()

john?.dog = fido
fido?.owner = john

fido = nil 
john = nil

Dog 객체가 소유하는 Person 객체가 Dog 객체가 살아있는 동안 항상 존재한다는 가정 하에 사용!

그럼 weak가 unowned에 비해 더 좋은 거 아닌가?

참조 방식메모리 접근 방식옵셔널 처리성능안전성
weak사이드 테이블을 통한 간접 접근옵셔널 언래핑 필요낮음높음
unowned직접 메모리 접근언래핑 불필요높음낮음 (런타임 크래시 위험)
  1. weak 참조는 다른 참조와 다르게 sideTable이란 곳에서 모든 객체의 RC 관리하기 때문에 추가적으로 시스템이 sideTable에 접근해 참조를 추적하고 변수를 nil로 바꿔주는 추가적인 메모리 관리로 성능에 영향 가능성

  2. 개발자 입장에서 Optional 변수를 사용하기 위해 옵셔널 언래핑으로 인한 조건문 처리로 성능에 영향 가능성

클로저에서의 순환 참조

캡처 리스트는 주변의 변수나 인스턴스를 캡처함으로써 해당 객체의 참조 횟수를 증가시켜 순환 참조가 발생할 수 있다.

class HTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = { [weak self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

캡처 리스트를 사용해서 주변 변수, 인스턴스를 캡처할 때, [weak self], [unowned self] 와 같이 참조를 관리하는 방식을 정하여 순환 참조 방지

JAVA의 Garbage Collection이랑 뭐가 다를까?

특성Java GCSwift ARC
메모리 해제 시점예측 불가능 (GC 실행 시점에 따라 다름)예측 가능 (참조 횟수가 0이 되면 즉시 해제)
런타임 오버헤드주기적인 GC 실행으로 인한 오버헤드 발생 가능낮음 (참조 횟수 관리로 인한 오버헤드 적음)
순환 참조 처리GC가 자동으로 처리개발자가 weak 또는 unowned로 관리 필요
실시간 처리 적합성낮음 (GC로 인한 일시적인 지연 가능성)높음 (즉시 메모리 해제로 지연 최소화)

가장 큰 특징은 GC의 경우, 주기적으로 GC가 동작하여 메모리를 관리하기 때문에 GC가 동작하는 시점에서 일시적으로 시스템이 느려질 수 있다.

다만 GC는 알아서 순환 참조를 해결해주기 때문에 개발자가 ARC에 비해 신경을 덜 써도 된다.

참고
https://codingmon.tistory.com/83

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

https://alexdremov.me/dive-into-swifts-memory-management

profile
안녕하세요! iOS 개발자입니다!

0개의 댓글