ARC란?

김준오·2021년 7월 17일
0

Swift

목록 보기
8/10

ARC 란?

Automatic Reference Counting 앱의 메모리 사용을 추적하고 관리

매번 값을 복사해서 전달하는 값 타입과는 달리 참조 타입의 경우에는 하나의 인스턴스가 참조를 통해 여러곳에서 접근하기 때문에 언제 메모리에서 해제되는지가 중요하다.
인스턴스가 적절한 시점에 메모리에서 해제되지 않으면 메모리 자원을 장비하게 된다!!

스위프트는 프로그램의 메모리 사용을 관리하기 위해 ARC를 사용.

클래스의 인스턴스를 생성할 때마다 ARC는 그 인스턴스에 대한 정보를 저장하기 위한 메모리 공간을 할당한다. 그 메모리 공간에는 인스턴스의 타입 정보와 함께 그 인스턴스와 관련된 저장 프로퍼티의 값 등을 저장 할 수 있다. 그 후 인스턴스가 더 이상 필요 없는 상태가 되면, 인스턴스가 차지하던 메모리 공간을 다른 용도로 사용 할 수 있도록 ARC가 메모리에서 인스턴스를 없앤다.

-> 따로 메모리를 어떻게 관리하는지 생각할 필요 없다.
클래스 인스턴스가 더이상 필요하지 않을때 해당 메모리를 자동으로 비워준다.
참조 될때마다 참조횟수가 +1 되고 nil을 할당해주면 참조 횟수가 -1 된다.

참조 횟수가 0이 되는 순간 인스턴스는 메모리에서 해제되며 해제되기 직전에 디 이니셜라이저를 호출한다.

ARC의 관리대상?

ARC가 관리해주는 참조 횟수 계산 (Reference Counting)은 참조 타입인 클래스의 인스턴스에만 적용된다. 구조체, 열거형값 타입으로 참조 횟수 계산과 무관. -> ARC에서 관리 x

MRC vs ARC

manual reference counting 에서는 retain, release 를 직접 해줘야 했지만, arc는 자동으로 retain, release를 삽입해서 referenceCount를 관리해주고, 0이 되면 deinit을 호출해서 메모리 해제

Heap 영역?

메모리 관리는 Data, Heap, Stack, Code 4가지의 가상 메모리 영역중 Heap 영역과 관련.
Heap는 class, closure 등의 참조형 자료들이 머무는 공간, 개발자가 동적으로 할당하는 메모리공간.
-> 따라서 관리가 필요하다.
관리를 위해서는 Heap 영역에 참조형 자료들이 얼마나 참조되고 있는지 카운팅 하고 이에 따라 메모리를 할당 및 제거하면 된다. 이것을 자동화 해주는것이 ARC!!

정리

Arc가 compile time에 실행되는데 어떻게 동적으로 실행되는 것들의 reference count를 세고 메모리 관리를 할수있나?

compile time에 코드분석을 통해 적절한 위치에 retain, release 등의 코드를 삽입해준다.

클래스의 새 인스턴스를 만들때 마다 해당 인스턴스에 대한 정보를 저장하기 위해 메모리할당.
메모리는 인스턴스의 타입 정보와 인스턴스의 값을 가진다.
인스턴스가 더이상 필요하지 않을 때 arc는 메모리 해제시켜 다른곳에 사용할 수 있게 해준다.

referenceCount는 동적 할당된 object를 표현하는 HeapObject struct에 접근 가능하다.

장점 :

컴파일 당시 이미 인스턴스의 해제 시점이 정해져 있어서 인스턴스가 언제 해제될지 예측 가능하다.
메모리 관리를 위한 시스템 자원을 따로 추가할 필요 없다.

단점 :

arc의 작동규칙을 모르고 잘못 사용시 인스턴스가 메모리에서 해제되지 않을 가능성 있다.
(강한 참조의 순환문제)

강한참조의 순환문제

ex)

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

class Room {
    let num: String
    
    init(num: String) {
        self.num = num
    }
    
    var host: Person?
    
    deinit {
        print("Room \(num) is being deinitialized")
    }
}

var domisol: Person? = Person(name: "domisol") // Person 참조 횟수 1
var room: Room? = Room(num: "606") // Room 참조 횟수 1

room?.host = domisol // Person 참조 횟수 2
domisol?.room = room // Room 참조 횟수 2

domisol = nil // Person 참조 횟수 1
room = nil // Room 참조 횟수 1

nil을 할당해줘도 참조횟수가 1로 인스턴스가 메모리에 남아있게된다.

var domisol: Person? = Person(name: "domisol") // Person 참조 횟수 1
var room: Room? = Room(num: "606") // Room 참조 횟수 1

room?.host = domisol // Person 참조 횟수 2
domisol?.room = room // Room 참조 횟수 2

room?.host = nil // Person 참조 횟수 1
domisol?.room = nil // Room 참조 횟수 1

domisol = nil // Person 참조 횟수 0
// " ~~ deinit "
room = nil // Room 참조 횟수 0
// " ~~ deinit "

room?.host = nil // Person 참조 횟수 1
domisol?.room = nil // Room 참조 횟수 1

이렇게 처리를 해줘야 메모리 해제가 가능하다.
하지만 매번 이렇게 하나씩 해제해주려면 코드가 길어지기도 하고 빼먹을수도 있다

--> 이럴때 약한참조를 쓴다!!

약한참조

자신이 참조하는 인스턴스의 참조횟수를 증가시키지 않는다.
참조 타입의 프로퍼티나 변수의 선언 앞에 weak 키워드를 써주면 그 프로퍼티는 자신이 참조하는 인스턴스를 약한참조 한다.
약한참조를 사용한다면, 자신이 참조하는 인스턴스가 메모리에서 해제될 수도 있다.
따라서 옵셔널 에서만 쓰일 수 있다.. 참조하던 인스턴스가 메모리에서 해제된다면 nil이 할당될 수 있어야 하기때문.

강한참조 문제를 약한 참조로 해결

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

class Room {
    let num: String
    
    init(num: String) {
        self.num = num
    }
    
    weak var host: Person? // 약한참조 !!!!
    
    deinit {
        print("Room \(num) is being deinitialized")
    }
}

var domisol: Person? = Person(name: "domisol") // Person 참조 횟수 1
var room: Room? = Room(num: "606") // Room 참조 횟수 1

room?.host = domisol // Person 참조 횟수 그대로 1 !!!!!
domisol?.room = room // Room 참조 횟수 2

domisol = nil // Person 참조 횟수 0, Room 참조 횟수 1 !!!!!!
// " domisol ~~ deinit "
print(room?.host) // "nil"

room = nil // Room 참조 횟수 0
// " room ~~ deinit "

강한 참조를 하게 될 경우 메모리가 부족할수도 있기떄문에 weak를 사용하는게 보편적이다.
복잡한 계층구조를 가지고있는 뷰 에서라면, 중간에 메모리에서 해제될 수도 있으므로 strong 사용한다.

profile
jooooon

0개의 댓글