본문 바로가기
⌨️ Language/swift

[Swift] 자동 참조 카운트

by hyebin (Helia) 2024. 4. 2.

자동 참조 카운트(ARC, Automatic Reference Counting)

  • 앱의 메모리 관리를 위해 사용
  • 자동으로 참조 횟수를 관리하기 때문에 대부분의 경우에 메모리 관리에 신경쓸 필요가 없고 ARC가 자동으로 사용하지 않는 인스턴스를 메모리에서 해지
  • 특정 경우에 ARC에서 메모리 관리를 위해 코드의 특정 관계에 대한 정보를 필요로 함
  • 참조 횟수는 클래스 타입의 인스턴스에만 적용되고 구조체, 열거형 등 값 타입에는 적용되지 않음

 

ARC 동작

  • 새로운 클래스 인스턴스를 만들 때 마다 ARC는 인스턴스 정보를 담는데 필요한 크기의 메모리를 할당
  • 메모리는 인스턴스에 대한 정보와 관련된 저장 프로퍼티 값을 갖고 있음
  • 인스턴스가 더이상 사용되지 않을 때 ARC는 그 인스턴스가 차지하고 있는 메모리를 해지해 공간을 확보
  • ARC는 아직 사용중인 인스턴스를 해지하지 않기 위해 하나라도 참조가 있는 경우 메모리에서 해지하지 않음

 

ARC 사용

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\\(name) is being initialized")
    }
    deinit {
        print("\\(name) is being deinitialized")
    }
}

var reference1: Person?
var reference2: Person?
var reference3: Person?

reference1 = Person(name: "John Appleseed")
// Prints "John Appleseed is being initialized"

reference2 = reference1
reference3 = reference1

현재 Person 인스턴스에 대한 참조 횟수는 3

reference1 = nil
reference2 = nil

현재 Person 인스턴스에 대한 참조 횟수는 1

reference3 = nil
// Prints "John Appleseed is being deinitialized"

Person인스턴스를 참조하고있는 것이 없으므로 ARC가 Person 인스턴스를 메모리에서 해지

 

클래스 인스턴스간 강한 참조 순환

클래스의 인스턴스간 강하게 상호참조하고 있는 경우 메모리에서 해제되지 않음

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

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

var john = Person(name: "John Appleseed")
var unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

 

두 개의 클래스가 서로 인스턴스를 소유하고 있는경우 서로를 참조하게되고, nil을 할당해 탐조가 해지하려고 하더라도 두 인스턴스는 해지되지 않음

메모리 누수 발생

 

강한 참조 순환 문제 해결

약한 참조

약한 참조로 선언하면 참조하고 있는 것이 먼저 메모리에서 해지되기 때문에 런타임에 자동으로 참조하고 있는 변수에 nil 할당

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

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

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

john = nil
// Prints "John Appleseed is being deinitialized"

unit4A = nil
// Prints "Apartment 4A is being deinitialized"

미소유 참조

참조 대상이 되는 인스턴스가 현재 참조하고있는 것과 같은 생애주기를 갖거나 더 긴 생애주기를 갖기 때문에 항상 참조에 그 값이 있다고 기대됨

따라서 ARC는 미소유 참조에는 절대 nil을 할당하지 않음 <옵셔널 타입 사용하지 않음>

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\\(name) is being deinitialized") }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\\(number) is being deinitialized") }
}

var john: Customer?

john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

john = nil
// Prints "John Appleseed is being deinitialized"
// Prints "Card #1234567890123456 is being deinitialized"

 

미소유 참조와 암시적 옵셔널 프로퍼티 언래핑

두 프로퍼티가 항상 값을 갖지만 한번 초기화 되면 절대 nil이 되지 않는 경우 미소유 프로퍼티를 암시적 옵셔널 프로퍼티 언래핑을 사용해 참조 문제를 해결

class Country {
    let name: String
    var capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

var country = Country(name: "Canada", capitalName: "Ottawa")
print("\\(country.name)'s capital city is called \\(country.capitalCity.name)")
// Prints "Canada's capital city is called Ottawa"

 

클로저에서의 강한 참조 순환

클로저에서는 self를 캡쳐하기 때문에 강한 참조 순환이 발생할 수 있음

클로저에서 강한 참조 순환 문제 해결

캡쳐 참조에 강한 참조 대신 약한 참조 혹은 미소유 참조 지정 가능

 

캡쳐리스트 정의

클로저의 파라미터 앞에 소괄호를 넣고 그 안에 각 캡쳐 대상에 대한 참조 타임을 적음

lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // closure body goes here
}
반응형

'⌨️ Language > swift' 카테고리의 다른 글

[Swift] 제네릭  (0) 2024.04.02
[Swift] 프로토콜  (0) 2024.03.29
[Swift] 에러 처리  (0) 2024.03.28
[Swift] 옵셔널 체이닝  (0) 2024.03.26
[Swift] 초기화  (1) 2024.03.25

댓글