강한 순환 참조 어떻게 확인할까?(LLDB, View Memory Graph Hierarchy, Memory Leak)

2023. 2. 10. 23:34IOS

반응형

이것이 정답은 아니겠지만 활용방안도 무궁무진하고, 좋은 기능인것 같습니다

강한 순환 참조 문제 코드

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") }
}
  • 해당 코드는 ARC 공식문서에 있는 강한 순환 참조가 발생하는 코드입니다

class ViewController: UIViewController {
    var john: Person?
    var unit4A: Apartment?

    override func viewDidLoad() {
        super.viewDidLoad()

        john = Person(name: "John")
        unit4A = Apartment(unit: "Xi")

        john?.apartment = unit4A
        unit4A?.tenant = john
    }

    @IBAction func didTapButton(_ sender: UIButton) {
        john = Person(name: "ABC")
        unit4A = Apartment(unit: "ABC")

        john?.apartment = unit4A
        unit4A?.tenant = john
    }
}
  • 해당 ViewController는 버튼을 눌렀을 경우 새로운 Person, Apartment 인스턴스를 생성하여 각각의 변수에 할당해주는 과정을 반복한다
  • 하지만 위 사진처럼 강한 순환 참조가 발생하므로 해당 클로저는 메모리에서 해제되지 않는다
  • 똑똑한 사람이라면 이 코드를 보고 곧바로 강한 순환 참조문제가 발생하게 된다는 것을 알아차리겠지만 나는 아마 알아차리지 못할 것이다

Memory Leak 발생 확인

  • 좌측 상단의 바에서 빨간색 사각형으로 표시된 부분을 클릭 할 경우 현재 동작하고 있는 앱을 여러 형태로 볼 수 있다.

  • 우리는 여기서, View Memory Graph Hierarchy를 통해 해당 메모리의 상관관계와 타입에 남아있는 메모리를 볼 수 있다.

  • 버튼을 누르면 Person, Apartment인스턴스가 지속적으로 생성됨으로 Apartment, Person에 관한 타입에 메모리가 남아있는게 보이고 해당 메모리가 낭비되고 있다는 표시가 뜬다.
  • 이것으로 우리는 메모리 누수가 발생한다는 것은 확인할 수 있다.
  • 그러나, 강한순환참조를 염두에 두고 이를 본다면 문제를 빠르게 찾아낼 수도 있겠지만 이것이 과연 강한순환참조의 문제로인한 누수인지 그 외의 문제인지 확인할 방법이 필요하다고 생각한다.
  • 타입들의 아래 부분은 타입 인스턴스의 주소를 나타내는데 이를 클릭하면 구조를 볼 수 있다.

  • 해당 구조를 보면 ViewController에 많은 것이 연결되어 있는 것을 볼수 있지만 그 중에서 우리가 작성한 타입 중 하나인 Apartment를 볼 수 있다.
  • 그러나 Person에 관한 인스턴스가 보이지 않아서 현재 ViewController가 가지고 있는 Person을 보기 위해서 이전 화면에서 메모리 누수가 발생하지 않는 주소 값을 클릭하였다.

  • 이 경우 우리는 ViewController에 연결되어있는 Person을 볼 수 있고 해당 PersonApartment를 가지고 있다는 것을 확인할 수 있다.

  • 우측 상단에 보여지는 화살표를 누를 경우 해당 Apartment에 연결되어있는 부분을 볼 수 있다.

 

  • 위 두 사진을 보면 알 수 있듯이 해당 타입들은 지속적으로 서로를 참조하는 모습을 보여주며 우측 상단 화살표를 계속 누를 경우 끝도없이 나아가는 모습을 볼 수 있다.
  • 이를 통해 우리는 ApartmentPerson이 지속적으로 서로 참조하는 구조와 이전 메모리 누수를 통해 우리는 이 두가지 타입이 강한순환참조로 인해 메모리 누수가 발생한다는 것을 알 수 있다.

강한순환참조 해결

class Person {
    let name: String
    init(name: String) { self.name = name }
    weak var apartment: Apartment?
    deinit { print("\(name) is being deinitialized") }
}
  • 기존 코드에서 Person타입이 참조하는 apartment변수를 weak를 선언함으로 강한순환참조 문제를 해결하였다.
  • 위 진행방법을 토대로 메모리 누수가 되는지?, 구조는 어떻게 되는지? 한번 알아보자

  • 버튼을 여러번 눌렀음에도 불구하고 ApartmentPerson이 우리가 원하던대로 하나의 인스턴스 주소만 가지고 있다는것을 확인할 수 있다.
  • 여기서 Apartment인스턴스를 클릭해보자

  • 해당 Apartment는 약한참조로 구현하지 않았으므로 정상적으로 Person을 참조하는 것처럼 보여지지만 SwiftWeakRefStorage를 통해 약한참조가 되어진다는 것을 확인할 수 있다.
  • 이번에는 Person의 인스턴스를 클릭해보자

  • PersonApartment와 조금 다른 구조를 가지고 있으며, ViewController에서 바로 화살표가 뻗는것이 아닌 malloc<64> 즉 메모리에 할당되는 것과 함꼐 출력되며 PersonSwiftWeakRefStorage를 참조하고 있다.
  • SwiftWeakRefStorage를 클릭하여도 더 이상 늘어나지 않는다

 

다만 이 방법도 정답은 아니다 unowned 키워드를 사용할 경우 구조를 봤을 때 지속적으로 늘어나기 때문에 메모리 누수를 확인하면서 해당 구조를 확인하는 방법 두가지를 적절하게 섞어야할 것 같다

추가로 구조내에서 해당 타입을 클릭할 경우 참조카운트를 확인할 수 있는데 unowned를 사용할 경우 구조가 끝 없이 늘어나는대신에 참조카운팅이 0이므로 이를 토대로도 확인을 하여 강한순환참조문제를 해결해야할 것 같다.

참고자료

반응형