본문 바로가기

iOS/iOS

iOS) 메모리 관리 (1/3) - ARC(Automatic Reference Counting)

안녕하세요~~ 소들입니다 👀

 

오늘은 지난 시간 메모리 구조에 이어

Swift를 사용할 때 메모리 관리가 어떤 식으로 되는지에 대해 

공부해볼 거예요 :)

 

ARC

 

면접 단골 질문이라죠? 깔깔

iOS 개발자라면 절대 몰라선 안 되는 중요한 개념이에요!

어렵게 생각할 수도 있지만

그리 어려운 개념이 아니랍니다 :)))) 차근차근 이해해 보아여

 

만약 이전 편을 안 보고 오신 분이라면

소들이의 메모리 기초 부터 보고 오세욥

 

그럼 이제 고고씽~~

포스팅은 편한 말투로 합니당~~!

 

 

 

 

1. 참조(Reference) 타입과 Heap

 

자, ARC를 들어가기에 앞서 먼저 참조와 힙에 대해서 공부해볼 것임 

이전 포스팅에서 힙이라는 놈의 특징에 대해 공부 했었음!!

 

왜 자꾸 힙에 집착하냐 묻는다면

ARC가 메모리 영역 중 힙 영역을 관리하는 것이기 떄문이셈

 

쨌든 다시

Swift는 힙에 메모리를 언제 할당한다!?

 

인스턴스, 클로저 등등 참조 타입(Reference Type)은 자동으로 힙에 할당 한다!

 

뭐 그랬음

그래서 우리가 힙을 직접 건들이지 않았어도

자동으로 할당하여 쓰고 있던 것임

 

 

.

.

 

힙에 할당된다는 것이 말로하면 잘 이해하기 힘들어서

실제 참조 타입을 선언할 경우 메모리가 어떤 식으로 할당되는지

예제를 통해 보겠음 :D

 

 

class Human {
    var name: String?
    var age: Int?
    
    init(name: String?, age: Int?) {
        self.name = name
        self.age = age
    }
}
 
let sodeul = Human(name: "Sodeul", age: 26)

 

 

자 Human이란 클래스가 있고

sodeul이라는 인스턴스를 생성하고 값을 초기화 했음

 

포스팅을 위해 예제에선 sodeul이 전역 변수가 됐지만,

보통 우리가 개발할 때 그렇듯, 그냥 어디 클래스에서 생성된 지역 변수라고 생각 해주셈!!!!

 

그럼 메모리엔 어떻게 저장 되냐면

 

 

 

 

이렇게 저장 됨

 

지역 변수 sodeul스택에 할당 되고

실제 Human 인스턴스에 할당 됨

 

스택에 있는 sodeul은 힙 영역에 있는 인스턴스를 참조하고 있는 형태임

따라서 sodeul 안엔 힙에 할당된 인스턴스의 주소값이 들어가 있음

 

 

참조기 때문에 당연히 

다음과 같은 작업을 해주면

 

 

let clone = sodeul

 

 

인스턴스가 복사되지 않는 것은 알고 있잖음?!?!

실제 메모리는 

 

 

 

 

이렇게 같은 힙 영역의 인스턴스를 가리키고 있겠지 XD

여기까진 어찌보면 Reference에 대한 기본 중에 기본 개념임

 

 

자, 근데 힙이라는 것의 특징 중 하나는

 

 

 

 

라는 것이었음

 

메모리를 해제하기 위해선 뭐 

release, free 

등등의 방법이 있는디 

 

우린 지금껏 인스턴스를 마음대로 할당하고 사용해 왔지만

단 한번도 저런 함수들을 통해  인스턴스를 직접 메모리에서 해제해 준 적은 없단 말임!?????!

 

그럼

스택에 있는 sodeul, clone이 함수 종료 시점에 사라지고 나면

 

 

 

 

힙에 남은 쓰이지도 않는 인스턴스는 누가 메모리 해제해준단 말임!?

메모리 leak으로 이어진단 말임!?

저 영역은 이제 어플 날라가기 전까지 아무도 못 쓴단 말임!?

 

 

아니ㅎㅎ..

ARC가 해제 해줌

 

 

 

 

2. ARC란 무엇일까?

 

ARC의 존재 이유를 설명하기 위해

위에 장장 길게 참조 타입의 메모리 할당에 대해 설명했음!!

 

위에서 대강 ARC가 힙의 메모리를 해제해준다고 말했지만

ARC의 정의에 대해 제대로 짚고 가겠음

 

 

 

 

ARC에 대한 Swift 문서 내용임 

내가 밑줄친 곳을 보면

 

ARC는 클래스 인스턴스가 더 이상 필요하지 않을 때

메모리를 자동으로 해제한다

 

라는 내용이 있음!! 이게 핵심임 :) 한 마디로

 

우린 지금껏 힙에 메모리를 자동 할당하며 사용해 왔지만

손수 메모리를 해제해주지 않아도 됐던 이유는

ARC라는 놈이 메모리를 자동으로 해제해주기 때문이다

 

오호 편하군..! 최종적으로 ARC에 대해 정의를 내리자면,

 

ARC란, 힙에 할당된 인스턴스의 메모리를 알아서 관리해주는 녀석이구나 :)

 

기본 정의는 이정도임!!!!! 

근데 이론에 대해 더 알아봅시다 :D

 

 

 

2-1. GC vs RC

 

힙 영역의 메모리를 관리하는 방법은 GC와 RC가 있음

먼저, 가장 유명한 자바의 GC(Garbage Collection)과 비교해 보겠음

(ARC는 RC에 포함됨)

 

 

자바의 GC와 스위프트의 RC의 가장 큰 차이점은

 

참조를 계산하는 시점

 

에 있음 ㅎㅎ 표로 정리해보겠음

 

 

 

GC

RC

참조 계산 시점

Run Time

▪ 어플 실행 동안 주기적으로 참조를 추적하여

사용하지 않는 instance를 해제함

Compile Time

▪ 컴파일 시점에 언제 참조되고 해제되는지

결정되어 런타임 때 그대로 실행됨

장점

▪ 인스턴스가 해제될 확률이 높음
(RC에 비해)

▪ 개발자가 참조 해제 시점을 파악할 수 있음
▪ RunTime 시점에 추가 리소스가 발생하지 않음

단점

▪ 개발자가 참조 해제 시점을 파악할 수 없음
▪ RunTime 시점에 계속 추적하는 추가 리소스가
필요하여 성능저하 발생될 수 있음

▪ 순환 참조가 발생 시 영구적으로
메모리가 해제되지 않을 수 있음

 

 

ㅎㅎㅎ

순환참조가 모야~~?

일단 스킵~~~ 이또한 뒤에 다룰 것

 

참조를 계산하는 시점이 언제인지,

장단점이 무엇인지 정도 알아두시길 ㅎㅎ

 

 

 

2-2. MRC(MRR) vs ARC

 

둘 다 RC지만 앞에 붙은

Manual vs Automatic

의 차이임

 

느낌이 옴!?

MRC는 힙에 메모리를 직접 할당/해제를 해주는 것임

 

 

왜 이걸 설명하냐면

2011년 이전엔 Objective-C가 이 방법을 사용했거든...ㅎㅎㅎㅎ

2011년 이후부터 옵젝씨도 ARC를 사용하기 시작함

(2014년에 나온 Swift는 당연히 ARC임)

 

그래서 오래된 코드 보면 가끔

 

 

 

 

이런 retain, release 같은 함수들을 볼 수도 있음!!!!

이것들이 바로 MRC를 사용할 때 쓰는 메서드들임

 

MRC에 대해선 이정도만 가볍게 알아도 좋으나,

나처럼 옵젝씨 유물 코드 만지는 분이라면

소들이의 MRC 부수기

이거 보고오셈 MRC에 대해 정리한 것임

 

 

 

.

.

 

자 이제 이론에 대해 다 공부 했음!!!!!

근데 ARC는 메모리를 어떻게 관리하길래, 알아서 인스턴스를 해제하는지 궁금하지 않음!?

 

얘가 정말 더이상 필요하지 않는 인스턴스인지,

아니면 어디 구석에선가 몰래 참조되고 있는 인스턴스인지

 

어떻게 구별해서 메모리 해제를 한단 말임!?

그리고 ARC의 단점에서

순환 참조는 뭔데 메모리에서 영영 해제되지 않는단 말임!?

 

이제 알아보자 XD

 

 

 

 

3. ARC의 메모리 관리 방법

 

어떻게 관리 하냐면ㅎㅎ..

 

Reference Count

 

을 이용하는 것임 ㅎㅎ

 

메모리의 참조 횟수(RC)를 계산하여, 참조 횟수가 0이 되면

더 이상 사용하지 않는 메모리라 생각하여 해제함

 

다시 말해 RC

 

이 인스턴스를 현재 누가 가리키고 있느냐 없느냐(참조하냐 안하냐)를

숫자로 나타낸 것임 ㅎㅎ

 

만약 참조 횟수가 10이라면

해당 인스턴스가 10군데에서 참조되고 있단 뜻이고,

참조 횟수가 0이라면

아무데서도 참조되지 않으니 필요없다! 메모리 해제해라! 란 뜻임

 

따라서!!!

⭐️ 모~~~~든 인스턴스는 자신의 RC 값을 가지고 있음 ㅎㅎ ⭐️

(인스턴스가 생성될 때 힙에 같이 저장된다고 함)

 

그럼 이 RC는 어떤 기준으로, 어떻게 셀까!!??

예제를 통해 보자 :)

 

 

 

3-1. 참조 횟수 Count Up (+)

 

참조 횟수가 +1이 되는 순간은

인스턴스의 주소값을 변수에 할당할 때

 

이렇게 말하니까 헷갈릴 거 같은데

쉽게 두 가지로 나눠서 설명 해보겠음

 

 

 ① 인스턴스를 새로 생성할 때 

 

 

let sodeul = Human(name: "Sodeul", age: 26)

 

 

아까 살펴봤던 이 코드는 실행되는 시점에

지역 변수 sodeul은 스택에 할당되고 실제 인스턴스는 힙에 할당된다 했잖음!?

그리고 sodeul엔 힙에 할당된 인스턴스의 주소값이 들어간다 했음

 

이렇게

인스턴스를 새로 생성할 때 (새로운 변수에 대입할 때)

해당 인스턴스에 대한 RC가 증가함

 

 

 

 

요롷겧ㅎㅎ

 

 

 ② 기존 인스턴스를 다른 변수에 대입할 때 

 

다음과 같이 

 

 

let clone = sodeul

 

 

기존 인스턴스를 다른 변수에 대입할 때도

당연히 참조에 의하기 때문에

 

 

 

 

 RC 값이 증가됨!!

clone이란 변수에 0x1111111이란 인스턴스의 주소값이 대입 되었으니!!

ㅎㅎㅎㅎㅎㅎ

 

 

 

3-2. 참조 횟수 Count Down (-)

 

그렇다면 참조 횟수는 언제 내려갈까?!

크게 4가지 경우로 설명 하겠음

 

 

 ① 인스턴스를 가리키던 변수가 메모리에서 해제되었을 때 

 

아까 sodeul 인스턴스를 생성하는 작업을 

다음과 같이 바꿔보겠음

 

 

func makeClone(_ origin: Human) {
    let clone = origin                          // ② Instance RC : 2
}
 
let sodeul = Human(name: "Sodeul", age: 26)     // ① Instance RC : 1
makeClone(sodeul)
                                                // ③ Instance RC : 1

 

 

위 코드와 주석이 이해가 간다면 잘 따라온겨!

 

sodeul이 생성되는 순간 인스턴스의 RC + 1이 됨

makeClone 함수가 실행 되어 sodeul을 참조하는 clone 변수가 생성되는 순간 인스턴스의 RC + 1이 됨

함수가 종료되어 지역변수 clone이 스택에서 해제되는 순간 인스턴스의 RC -1이 됨

 

메모리로 예로 보면 쉬움

 

 

[1] makeClone 함수가 실행된 시점

 

 

 

 

[2] makeClone 함수가 종료되어 메모리에서 해제된 시점

 

 

 

 

RC가 1인 이유는,  sodeul이란 변수에 대한 참조값 1이 남은 것임

자자, 정리하자면

 

인스턴스를 참조하고 있던 변수가 메모리에서 해제되면

해당 인스턴스의 RC 값은 -1이 됨

 

 

 ② nil이 지정되었을 때  

 

먼저, nil이 지정된단 소리는 당연히 옵셔널 타입이라는 소리임

 

 

var sodeulHuman= .init(name: "Sodeul", age: 26)      // ① Instance RC : 1
var clone = sodeul                                       // ② Instance RC : 2
 
clone = nil                                              // ③ Instance RC : 1
sodeul = nil                                             // ④ Instance RC : 0 (메모리 해제)

 

 

차근차근 생각하면

전혀 어렵지 않을 거라구 생각함 🥶

만약 위 코드를 다 실행했다면 sodeul의 RC 값이 0이 되었으므로 

해당 인스턴스는 ARC에 의해 메모리에서 해제될 것임

 

 

 ③ 변수에 다른 값을 대입한 경우 

 

이건 RC + 조건하고도 연관지어 생각하면 쉬운데,

예제로 보면 빠르게 이해할 수 있음

 

 

var sodeulHuman= .init(name: "Sodeul", age: 26)    // ① Sodeul Instance RC : 1
var cloneHuman= .init(name: "Sodeul2", age: 26)    // ② Clone Instance RC  : 1
 
sodeul = clone                                         // ③ Clone Instance RC  : 2, Sodeul Instance RC : 0 (메모리 해제)

 

 

머 이런 내용임

 

sodeul에 clone의 값을 대입하게 되면

sodeul의 RC는 -1

clone의 RC는 +1

 

당연히 sodeul 변수에 저장된 주소값이 바꼈으니

참조 카운터도 변하는 것임

 

따라서 sodeul이 가리키던 인스턴스는 RC가 0이 되었으므로

 ARC에 의해 자동으로 메모리에서 해제

 

 

 ④ 프로퍼티의 경우, 속해 있는 클래스 인스턴스가 메모리에서 해제될 때 

 

이것도 예제로 보면 쉬움

 

 

class Contacts {
    var email: String?
    var address: String?
    
    init(email: String?, address: String?) {
        self.email = email
        self.address = address
    }
    deinit { print("Contacts Deinit)" } }
}

class Human {
    var name: String?
    var age: Int?
    var contacts: Contacts= .init(email: "o_o@naver", address: "Suwon")
    
    init(name: String?, age: Int?) {
        self.name = name
        self.age = age
    }
    deinit { print("Human Deinit)" } }
}
 
let sodeul: Human= .init(name: "Sodeul", age: 26)
sodeul = nil

 

 

예제가 클래스 선언 때문에 좀 길어졌는데 

간단하게 설명하면

 

Human이라는 클래스 안에 contacts라는 클래스 인스턴스가 프로퍼티로 존재

따라서 Human 인스턴스 sodeul을 생성하면 sodeul은 물론,

프로퍼티인 contacts 인스턴스도 생성되며 두 인스턴스 각각의 RC가 증가됨

 

메모리로 보면 다음과 같이!!

 

 

 

 

하지만

contacts는 sodeul이 가리키던 인스턴스에 소속된 프로퍼티이기 때문에,

sodeul이 가리키던 인스턴스가 메모리에서 해제될 경우, contacts의 RC가 하나 감소

음.. 이 부분이 어려울 수 있어서 단계로 설명해주겠음 :)

 

 

[1] sodeul에 nil을 할당한 순간, sodeul이 가리키고 있던 Human Instance의 RC가 1 감소한다

 

 

 

 

[2] Human Instance의 RC가 0이 되었으니 메모리에서 해제된다

이때, Human Instance가 품고 있던 contacts란 프로퍼티도 같이 메모리에서 해제되니, 

contacts란 프로퍼티가 가리키고 있던 Contacts Instance의 RC가 1 감소한다

 

 

 

 

[3] Contacts Instance의 RC도 0이 되어 메모리에서 해제된다

 

 

 

 

이런 루틴임!! 유의할 점은!!!

 

sodeul이 가리키던 Human 인스턴스가 메모리에서 해제된다 해서,

프로퍼티인 contacts가 가리키던 Contacts 인스턴스의 메모리도 같이 해제되는 것이 아님!!

Contacts 인스턴스의 RC가 -1 감소할 뿐!

 

만약 RC가 감소했는데 위 예제처럼 0이 되면,

그땐 Contacts 인스턴스도 메모리에서 해제되는 것임

 

실제 위 예제의 실행결과를 찍어보면

 

 

 

 

sodeul이 가리키던 Human 인스턴스가 먼저 RC 0이 되어 deinit 되고,

Human 인스턴스의 프로퍼티 contacts가 가리키던 Contacts 인스턴스도 RC가 0이 되어 deinit 됨

 

 

 

 

 

 

 

 

.

.

.

 

아이고 힘들어라...

원래 strong, weak, unowned, 순환참조 등 

이번 포스팅에서 할라 했는데

 

생각보다 자세하게 쓰다보니  너무 길어져서

못다른 내용들은 다음 포스팅에서 하겠습니다..!!!!!!!!!!1

 

 

 

 

질문 및 피드백은 언제나 환영입니다 :D



Calendar
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
최근 댓글
Visits
Today
Yesterday