Swift

4. iOS GC와 ARC의 차이점, 순환참조

Daesiker 2022. 7. 19. 14:29
반응형

4-1. 개요

iOS 메모리 관리 기법에 ARC가 있다면 타 언어에서는 GC(Garbage Collector)가 있다. 거의 비슷한 방식으로 동작하지만 둘의 차이점은 엄연히 존재한다. 오늘은 이 차이점에 대해 공부하고 순한 참조가 무엇이고 어떤 식으로 발생하는지 알아볼 예정이다.

https://daesiker.tistory.com/104

4-2. GC(Garbage Collection)

GC방식은 프로그램이 실행 중일 때 Garbage Collector라는 것이 동적으로 감시를 하고 있다가, 더 이상 사용할 필요가 없다고 여겨지는 인스턴스를 메모리에서 해제시키는 방식이다. ARC랑 비슷하지만 여기에는 몇가지 차이점이 있다.

GC

  • 참조 계산 시점
    • 런 타임
    • 프로그램을 실행하는 동안 주기적으로 검사하여 사용하지 않는 instance 해제한다.
  • 장점
    • 인스턴스가 해제될 확률이 높다.
  • 단점
    • 개발자가 참조 해제 시점을 파악할 수 없다.
    • 프로그램 실행 도중에 계속 추적하여 성능저하가 발생한다.

ARC

  • 참조 계산 시점
    • 컴파일 타임
    • 컴파일 시점에 언제 참조되고 해제되는지 결정되어 런타임 때 그대로 실행된다.
  • 장점
    • 개발자가 참조 해제 시점을 파악할 수 있다.
    • RumTime 시점에 추가 리소스가 발행하지 않는다.
  • 단점
    • 순환 참조가 발생한다.

GC와 ARC에 가장 큰 차이점은 계산 시점이다. ARC에 경우 런타임 때 메모리 해제 시점이 다 정해져 있고 우리는 RC 카운트가 0이 되는 시점에 메모리가 해제되는 것을 알 수 있다. 하지만 GC는 런타임 때 Garbage Collector라는 것이 작동하여 일정시간에 참조하지 않는 메모리를 체크하여 해제를 한다. 그 일정시간을 모르기 때문에 우리는 언제 해제되는지 알 수 없다. 그리고 이렇게 Garbage Collector가 주기적으로 돌아가기 때문에 성능저하가 발생할 우려가 있다. GC는 이런 단점이 존재하지만 ARC에는 치명적인 단점인 순환참조가 발생할 수 있다.


4-3. 순환참조

앞서 말한 ARC의 단점 중에 순환 참조라는 말을 언급하였는데, 순환참조는 두 인스턴스 서로가 서로를 참조할 때 발생하는 일이다. 예제를 통해서 확인해 보자.

-SourceCode

class Man {
    var name:String
    var woman:Woman?

    init(_ name: String) {
        self.name = name
        print("\(name) init")
    }

    deinit {
        print("\(name) deinit")
    }

}

class Woman {
    var name:String
    var man: Man?

    init(_ name: String) {
        self.name = name
        print("\(name) init")
    }

    deinit {
        print("\(name) deinit")
    }

}

//Step1: 2개의 인스턴스 메모리 할당
var boy:Man? = Man("daesiker")
var girl:Woman? = Woman("dlwlrma")

//Step2: 서로 참조
boy?.woman = girl
girl?.man = boy

//Step3: 메모리 해제
boy = nil
girl = nil

-Console

daesiker init
dlwlrma init

Woman 인스턴스와 Man 인스턴스로 구성된 지역변수 boy, girl을 메모리에 할당하고, 이 안에 옵셔널 멤버인 man과 woman에 서로 참조를 하고 boy와 girl을 초기화를 하였다. 하지만 Console을 보면 메모리 할당은 잘되었지만, 초기화가 제대로 안된것을 출력 결과를 통해 알 수 있다. 왜 그런지 자세히 살펴보자.

Step1

Woman 인스턴스인 girl과 Man 인스턴스인 boy를 메모리에 할당하였다. 이 때 RC카운트는 둘다 1이다.

Step2

Man 인스턴스의 woman 멤버를 대입연산을 통해 girl로 참조하였고 반대로 Woman 인스턴스도 마찬가지로 man이란 멤버에 boy를 참조하였다. ARC의 연산 방법에 따라 참조를 하면 자동으로 RC가 증가하므로 이 때 RC는 2가 된다.

Step3

지역변수 boy와 girl의 메모리를 초기화 해주었다. Man 인스턴스와 Woman 인스턴스의 참조가 하나 씩 줄어들었으므로, RC가 둘다 1씩 감소한다.

이 때, 힙영역에 있는 인스턴스들이 실질적으로 참조하는 지역 변수가 없지만 RC가 0이 아니라 1이라서 힙영역에서 해제되지 않고 남아있는 것이 순환참조라고 한다. 이것을 방지하는 방법으로는 가장 원시적인 방법으로는 boy와 girl을 해제하기 전에 인스턴스 멤버를 먼저 초기화 해주는 것이다.

boy?.woman = nil
girl?.man = nil

이렇게 하고 다시 출력 결과를 보면 deinit이 정상적으로 출력되는걸 확인할 수 있다. 근데 순환참조가 나올 때 마다 이렇게 신경을 쓰는 것은 생각보다 쉽지 않다. 다음 포스팅을 통해 참조를 보다 디테일하게 하는 방법을 포스팅할 예정이다.

참조
https://babbab2.tistory.com/27
반응형