본문 바로가기
⌨️ Language/swift

[Swift] ARC

by hyebin (Helia) 2023. 2. 7.

ARC (Automatic Reference Counting)

  • 자동으로 메모리를 관리해 주는 방식
  • 대부분의 경우에 개발자는 메모리 관리에 신경을 쓸 필요가 없음
  • ARC는 더 이상 필요하지 않은 클래스의 인스턴스를 메모리에서 해제하는 방식으로 동작
  • 메모리 관리는 메모리 영역 중, 힙 영역과 관련
    • 힙 영역은 참조(Reference) 형 타입인 클래스, 클로저 등이 보관
    • 값(Value) 타입인 구조체, 열거형 등은 메모 리 관리 대상이 아님
  • 힙 영역의 참조형 자료들이 프로그램 상에서 얼마나 참조되는지 숫자를 세어서, 메모리 가 자동으로 할당 및 제거하도록 관리하는 것

ARC 동작

  • ARC는 클래스 인스턴스를 생성하였을 때, 메모리를 할당
  • 클래스 인스턴스가 더이 상 필요하지 않을 때, ARC는 해당 메모리를 해제
  • ARC는 잘못된 메모리 접근을 막기 위해 얼마나 많은 프로퍼티들이 각각의 클래스 인스턴스를 참조하고 있는지 숫자를 카운트
  • Swift에서는 각 객체마다 참조 횟수를 가지고 있고, 이 개수를 통해 메모리가 해제되어야 하는지를 결정
  • 참조 횟수가 0이 되는 순간까지 ARC는 객체를 메모리에서 해제하지 않음

 

강한 참조 순환 (Strong Reference Cycle)

  • 두 개의 참조형 타입의 데이터가 서로 강한 인스턴스로 참조하고 있어서, 참조 개수가 0개가 되지 않는 상황을 의미
  • 이들 인스턴스는 자신을 선언한 변수가 nil 이 되어도, 서로에 대한 강한 참조 때문에 메모리를 빠 져나가지 못하게 되고, 이로 인해 메모리 누수(Memory Cycle) 발생
  • 강한 참조 순환 문제를 해결하기 위해서는 두 가지 방법이 있음
    • 하나는 weak 참조, 다른 하나는 unowned 참조를 사용하는 것
    • weak 참조, unowned 참조 모두 ARC에서 참조 횟수를 증가시키지 않고 인스턴스를 참조하여 강한 참조 순환 문제를 해결가능
class Person {
    let name: String
    var apartment: Apartment?
 
    init(name: String) {
        self.name = name
    }
 
    deinit {
        print("\(name) is being deinitialized")
    }
}

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

 

약한 참조 (Weak References)

  • 참조하고 있는 인스턴스가 먼저 메모리에서 해제될 때 사용
  • 강한 참조와 다르게 참조 횟수를 증가시키지 않음
  • 변수의 선언 앞에 weak 키워드를 작성하여 사용하며, nil 이 할당될 수 있어야 하기 때문에 항상 옵셔널 타입
  • ARC에서 약한 참조에 nil을 할당하면 프로퍼티 옵저버는 실행되지 않음
class Person {
    let name: String
    var apartment: Apartment?
 
    init(name: String) {
        self.name = name
    }
 
    deinit {
        print("\(name) is being deinitialized")
    }
}

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

 

미소유 참조 (Unowned References)

  • 참조하고 있는 인스턴스가 같은 시점 혹은 더 뒤에 해제될 때 사용
  • 해당 인스턴스에 대하여 Reference Counting을 하지 말라고 선언하는 것
  • 변수의 선언 앞에 unowned 키워드를 작성하여 사용하며, 옵셔널 타입 일 수도 있고, 아닐 수도 있지만 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")
     }
}

 

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

  • 클래스와 마찬가지로, 참조 타입인 클로저에서도 강한 참조 순환이 발생 가능
  • 클로저 내부에서 self를 캡처(capture)해서 사용할 수 있기 때문에, self를 약한 참조 혹은 미소유 참조로 지정하면 강한 참조 순환 문제를 해결 가능
    • capture: 자신이 정의되는 시점에 주변 환경으로부터 여러 상수나 변수에 대한 참조를 잡아내어 저장
lazy var someClosure: () -> String = { [weak self] in
 // ..
}
반응형

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

[Swift] Escaping Closure  (1) 2023.05.20
[Swift] 접근 제어 (Access Control)  (0) 2023.02.07
[Swift] 제네릭 (Generic)  (0) 2023.02.07
[Swift] 에러 처리  (0) 2023.02.07
[Swift] 고차함수  (0) 2023.02.06

댓글