[iOS-Swift] ARC

TIL 13일 차 - Swift 문법 ARC

ARC (Automatic Reference Counting)

Reference Type의 인스턴스 메모리 관리를 자동으로 해주는 기능임

(Value Type의 인스턴스는 ARC가 관리하지 않음)

 

메모리 관리를 직접 하지 않아도 ARC가 자동으로 메모리 관리를 처리해 줌

- 인스턴스가 더 이상 필요하지 않을 때 메모리에서 자동으로 해제

 

Reference Type의 인스턴스를 참조할 때, 참조 카운트가 증가하며 이를 strong 참조라고 함 (default)

- 참조 카운트의 증가를 원치 않을 경우 weak, unowned 참조를 사용할 수 있음

 

 

ARC의 동작 방식

 

1. 인스턴스 생성과 메모리 할당

- class의 새로운 인스턴스를 생성할 때마다 ARC는 해당 인스턴스에 대한 정보를 저장할 메모리 공간을 할당함

- 이 공간에는 인스턴스의 타입 정보와 프로퍼티의 값이 저장됨

 

2. 참조 카운트 관리

- ARC는 사용 중인 인스턴스가 메모리에서 해제되지 않도록, 몇 개의 프로퍼티, 상수, 변수가 인스턴스를 참조하고 있는지 추적함

- 참조 시작 시 : 카운팅 + 1

- 참조 종료 시 : 카운팅 - 1

- 최종적으로 참조카운트가 0이 되면 인스턴스는 메모리에서 해제됨

 

3. 메모리 해제

- 참조카운트가 0인 된 인스턴스는 더 이상 필요하지 않으므로 ARC가 해당 인스턴스가 사용했던 메모리를 해제함

- 만약 참조카운트가 0이 되기 전에 인스턴스가 해제되면, 그 이후 해당 인스턴스에 접근할 경우 앱이 크래시 발생할 수 있음

 

 

ARC 동작 시기

- ARC는 컴파일타임에 인스턴스의 참조 카운트를 증가 or 감소시키는 코드를 자동으로 삽입함

 

Memory Leak (메모리 누수)

- 사용이 끝난 인스턴스가 메모리에서 해제되지 않고 남아있는 현상

- 이렇게 메모리 공간이 불필요하게 사용되면 성능 저하 발생 가능

 

강한 순환 참조(Strong Reference Cycle)

- iOS에서 자주 발생하는 Memory Leak의 원인 중 하나

class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
    var apartment: Apartment? // Apartment 참조
    deinit  {
        print("\(name) is being deinitialized")
    }
}

class Apartment {
    let unit: String
    init(unit: String) {
        self.unit = unit
    }
    var tenant: Person? // Person을 참조
    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

var john: Person?
var unit4A: Apartment?

// 두 인스턴스를 생성하고 각각 강한참조로 참조하고 있는 상태 (디폴트값: strong)
john = Person(name: "John") // RC : 1
unit4A = Apartment(unit: "4A") // RC : 1

john!.apartment = unit4A // john의 아파트에는 unit4A를 할당해 RC가 +1되어 2가 됨
unit4A!.tenant = john // unit4A의 거주자에는 john을 할당해 RC가 +1 되어 2가 됨

// 참조가 종료되었으므로 각각의 인스턴스 RC에 -1을 함
john = nil // RC : 1
unit4A = nil // RC : 1

// 각각의 인스턴스가 종료되었지만 RC가 아직 1이므로 메모리에서 해제 되지 않음

 

 

강한 순환 참조 해결 방법

- 참조 카운트를 올리지 않으면 강한 순환 참조를 방지할 수 있음

- 참조 카운트를 올리지 않기 위해 strong이 아닌 weak, unowned를 사용하면 됨

class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
    weak var apartment: Apartment? // Apartment weak 참조
    deinit  {
        print("\(name) is being deinitialized")
    }
}

class Apartment {
    let unit: String
    init(unit: String) {
        self.unit = unit
    }
    weak var tenant: Person? // Person을 weak 참조
    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

var JH: Person?
var unit1004A: Apartment?

JH = Person(name: "John") // RC : 1
unit1004A = Apartment(unit: "4A") // RC : 1

JH!.apartment = unit1004A // Person의 apartment가 weak이므로 RC 변동 없음
unit1004A!.tenant = JH // Apartment의 tenant가 weak이므로 RC 변동 없음

// 참조가 종료되었으므로 각각의 인스턴스 RC에 -1을 함
JH = nil // RC : 0
unit1004A = nil // RC : 0

// RC가 0이 되어서 각각의 인스턴스가 메모리에서 해제 됨

 

 

 

객체를 참조하는 세 가지 방법

 

1. strong (강한 참조) (기본값)

- 객체를 참조 시작하면 카운트 +1, 객체 참조가 종료되면 카운트 -1

 

2. weak (약한 참조)

- 객체를 참조, 종료하더라도 참조 카운트가 변화 X

- nil을 허용함

 

3. unowned (미소유 참조)

- 객체를 참조, 종료하더라도 참조 카운트가 변화 X

- weak과 달리 nil이면 크래시 발생함