[iOS] 메모리 관리_ARC

JJOOEE__·2024년 7월 10일
0

iOS

목록 보기
3/4
post-thumbnail

🍎 1. 메모리와 디스크

🍏 1) 메모리

  • RAM (Random Access Memory):
    • 목적: 운영체제와 애플리케이션이 실행되는 동안 데이터를 임시로 저장함
    • 특성
      • 휘발성 메모리로 영구적 데이터를 저장하지 않음, 일시적 메모리 저장을 위해 사용
      • 앱 실행중 메모리에 저장된 데이터는 앱을 종료하면 함께 사라짐
      • 앱 자체도 데이터 덩어리이기 때문에, 메모리에 올라감
      • 메모리에 저장된 데이터는 앱 메모리에서 내려올때 같이 내려옴
      • RAM용량이 클수록, 동시 실행 시킬수 있는 앱의 총량이 높아짐
      • 디스크보다 속도가 빠름
      • 디스크에 비해 용량이 작음
      • 비 휘발성 메모리 도 존재함 (ROM)
  • ROM (Read-Only Memory):
    • 목적: 시스템 부팅에 필요한 기본 프로그램과 OS 이미지가 저장됨
    • 특성:
      • 비휘발성이며, 전원이 꺼져도 데이터가 유지됨
      • 펌웨어와 같은 불변의 데이터를 저장함

🍏 2) 디스크

  • 영구적인 데이터를 저장하는 곳, 비 휘발성 장치임
    • 앱 실행중 남은 디스크에 저장된 데이터는 앱을 종료해도 디스크에 남음
  • 파일, 문서, 프로그램 등 상대적으로 용량이 큰 정보를 담음
  • 메모리에 비해 속도가 느림
  • UserDefaults, CoreData 를 활용해서 디스크에 데이터를 저장

🍀 메모리 vs 디스크 어떤걸 사용해야할까?

🌈 메모리(RAM)를 사용하는 경우

  1. 일시적 데이터 저장
    • 카운터 앱의 number 변수 : 카운트는 앱이 실행되는 동안에만 필요하며, 앱이 종료되면 데이터가 사라져도 문제없음
    • 웹 브라우저의 페이지 로딩 데이터 : 현재 로딩 중인 웹 페이지 데이터는 메모리에 저장되어 빠른 렌더링을 지원
  2. 빠른 데이터 접근 필요
    • 현재 열려 있는 문서의 편집 데이터 : 텍스트 편집기에서 작성 중인 내용은 빠른 접근과 수정이 필요하므로 메모리에 저장
    • 게임 플레이 중의 실시간 데이터 : 게임에서 캐릭터의 위치, 상태 등 실시간 데이터는 빠른 반응이 필요하기 때문에 메모리에 저장
  3. 애플리케이션 실행 상태 유지
    • 운영체제 및 실행 중인 애플리케이션 데이터 : 운영체제와 현재 실행 중인 프로그램의 상태 정보는 메모리에 저장되어 빠른 전환과 실행을 지원

🌈 디스크를 사용하는 경우

  1. 영구적인 데이터 저장
    • 전화번호부 앱의 친구 이름과 전화번호 데이터 : 사용자가 입력한 연락처 정보는 앱 종료 후에도 유지되어야 하므로 디스크에 저장
    • 게임 앱에서 유저의 스테이지 클리어 정보 : 유저의 게임 진행 상황은 영구적으로 저장되어야 하므로 디스크에 저장
  2. 사용자 설정 및 구성
    • 애플리케이션 설정 및 사용자 설정 : 사용자가 설정한 앱의 구성 정보와 설정은 디스크에 저장되어야 재시작 후에도 유지
    • 운영체제의 구성 파일 및 시스템 설정 : 운영체제의 기본 설정과 시스템 구성 파일은 디스크에 저장되어야 지속
  3. 대용량 데이터 저장
    • 미디어 파일(사진, 비디오, 음악) : 사용자가 생성하거나 다운로드한 대용량 미디어 파일은 디스크에 저장
    • 데이터베이스 파일 : 애플리케이션의 주요 데이터베이스 파일은 영구 저장소인 디스크에 저장

🍎 2. swift 메모리 관리 시스템 [ Reference Counting ]

기초 메모리 관련 게시글을 먼저 공부 하시길

🍏 1. Reference 타입과 Heap

  • Heap에 대해 왜 알아야 하냐??

    • 오늘 공부할 ARC가 힙 영역을 관리 하는 것 이기 때문이다!!
  • swift는 Heap에 메모리를 언제 할당 한다??

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

힙에 할당한다는 말을 이해하기 위해 아래 예시 코드를 확인

class Fruit {
    var name: String?
    var color: String?

    init(name: String?, color: String?){
        self.name = name
        self.color = color
        }
}

let banana = Fruit(name: "Banana", color: "Yellow")

Fruit라는 class가 banana라는 인스턴스를 생성하고 값을 초기화 했음

banana는 스택에 할당 되고 Fruit는 힙에 할당 됨

스택의 banana는 힙 영역의 인스턴스를 참조하는 형태임
따라서 banana안엔 힙에 할당된 Fruit의 인스턴스 주소값이 들어가 있음!!

let clone = banana

를 해주면 인스턴스가 복사 되지 않음!!


같은 Heap 영역의 인스턴스를 가리키고 있음!
여기까지가 Reference의 기본 개념임!!

  • Reference란??
    참조 타입 (Reference Type)
    • 정의: 메모리 주소를 참조하는 타입입니다.
    • 예시: class 등.
    • 특징: 변수에 값을 할당하거나 함수에 전달할 때 동일한 인스턴스를 참조합니다.

Heap의 특징중 하나인
" 사용하고 난 후 반드시 메모리해제를 해줘야한다! "
스위프트는 ARC를 통해 힙에 할당된 메모리가 더 이상 쓸모 없어지면(참조되지 않으면) 자동으로 해제해주기 때문에 우리가 free, release등으로 해제한적이 없는것 ! 메모리 기본 글에서 봤던 이 내용에 대해 제대로 살펴볼 것!

메모리를 해제하기 위해선 release, free 등 방법이 있는데 코드를 작성하며 사용해본 적이 없을것이다
그러면 어떻게? 인스턴스 메모리 해제를 해준적이 없었는데???

힙에 이렇게 남아 쓰이지 않는 인스턴스 메모리는 누가 해제해줌???
이렇게 남아있으면 이게다 memory leak (메모리 누수)가 되는것!!

이런 메모리 누수를 자동으로 해제해 주는게 바로 ARC이다!

🍏 2. ARC(Automatic Reference Counting)란?

🍀 Swift 공식 문서 속 ARC

  • ARC는 클래스 인스턴스의 메모리 사용을 자동으로 추적하고 관리합니다.
  • ARC는 참조 타입에 대한 참조 카운트를 유지
  • 👉인스턴스가 더 이상 필요 없을 때 메모리를 자동으로 해제👈
  • 이를 통해 개발자는 명시적으로 메모리 해제를 관리할 필요가 없음
  • Swift는 ARC를 통해 메모리 관리를 간소화하고, 메모리 누수를 방지할 수 있도록 설계됨

우리가 지금껏 힙에 메모리를 자동 할당하며 사용해 왔지만 손수 메모리를 해제해주지 않아도 됐던 이유가 바로 인스턴스가 더 이상 필요 없을 때 메모리를 자동으로 해제해주는 ARC가 있었기 때문이다!

🍀 자바의 GC(Garbage Collector) vs 스위프트의 RC(Reference Countin)

Garbage CollectorReference Counting
참조 계산 시점Run Time
▪ 어플 실행 동안 주기적으로 참조를 추적하여
사용하지 않는 instance를 해제함
Compile Time
▪ 컴파일 시점에 언제 참조되고 해제되는지
결정되어런 타임 때 그대로 실행됨
장점▪ 인스턴스가 해제될 확률이 높음
(RC에 비해)
▪ 개발자가 참조 해제 시점을 파악할 수 있음
▪ RunTime 시점에 추가 리소스가 발생하지 않음
단점▪ 개발자가 참조 해제 시점을 파악할 수 없음
▪ RunTime 시점에 계속 추적하는
추가 리소스가 필요하여 성능저하 발생될 수 있음
▪ 순환 참조가 발생 시
영구적으로 메모리가 해제되지 않을 수 있음

🍀 MRC(MRR) VS ARC

  • MRC = Manual Reference Counting
  • ARC = Automatic Reference Counting

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

🍏 3. ARC(Automatic Reference Counting)의 메모리 관리 방법

Reference Counting 을 이용한 메모리 관리방법!

  • 메모리 참조 횟수(RC)를 계산하여,
    참조횟수가 0이 되면 더이상 사용하지 않는 메모리로 생각하여 해제함

  • 인스턴스를 현재 누가 가리키고 있는지 없는지(참조여부)를 숫자로 나타낸것 = RC

따라서 모든 인스턴스는 RC값을 가지고 있음!!
[ 인스턴스가 생성 될 때, 힙에 같이 저장됨 ]

🍀 ① 참조 횟수 Count Up(+)

  • 참조 횟수가 +1이 되는 순간은?
    • 인스턴스의 주소값을 변수에 할당할 때

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

let banana = Fruit(name: "Banana", color: "Yellow")

위에서 살펴본 이 코드는 실행되는 시점에
Fruit는 스택에 할당되고 실제 인스턴스는 힙에 할당됨
banana는 힙에 할당된 인스턴스의 주소값이 들어감!

이렇게 인스턴스를 새로 생성할 때 (새로운 변수 대입시!)
해당 인스턴스에 대한 RC가 증가함

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

let clone = banana

🍀 ② 참조 횟수 Count Down(-)'

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

func makeClone(_ origin: Fruit) {
    let clone = origin                          // ② Instance RC : 2
}

let banana = Fruit(name: "Banana", color: "Yellow")     // ① Instance RC : 1
makeClone(banana)								// ③ Instance RC : 1

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

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

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

makeClone 함수 끝난 시점에 RC = 1인 이유는?

  • banana에 남은 참조값 1이 있기 때문에!!

🌈 ② nil이 지정되었을 때

nil 타입 = optional

var banana : Fruit? = .init(name: "Banana", color: "Yellow")  // ① Instance RC : 1
var clone = banana					// ② Instance RC : 2

clone = nil							// ③ Instance RC : 1
banana = nil						// ④ Instance RC : 0 (메모리 해제)

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

var banana : Fruit? = .init(name: "Banana", color: "Yellow")	// ① banana Instance RC : 1
var clone : Fruit? = .init(name: "Banana2", color: "Yellow")	//② clone Instance RC : 1

banana = clone		// ③ clone Instance RC : 2, banana Instance RC : 0

banana에 clone 값을 대입하면
banan 변수에 저장된 주소값이 바뀜 = 참조 카운터 바뀜
banana의 RC = -1
clone의 RC = +1

banana가 가리키던 인스턴스 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: "leejh950417@naver.com", address: "JeonJu")

    init(name: String?, age: Int?){
        self.name = name
        self.age = age
    }
    deinit { print("Human Deinit") }
}

let jjooee: Human? = .init(name: "JJOOEE", age: 30)
jjooee = nil

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

따라서 Human 인스턴스 jjooee를 생성하면 jjooee는 물론,

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

① jjooee = nil 할당한 순간, jjooee가 가리키던 Human 인스턴스가 1감소

② Human 인스턴스의 RC가 0이 되었으니 메모리에서 해제되는 때, 휴먼 인스턴스가 품고 있던 contacts란 프로퍼티도 같이 메모리에서 해제됨 >> contacts란 프로퍼티가 가리키던 contacts란 인스턴스의 RC가 1감소한다

③ contacts란 인스턴스의 RC도 0이되며 메모리에서 해제됨

업로드중..

💡 주의 💡
jjooee 가 가리키던 Human의 인스턴스가 메모리에서 해제 된다고
프로퍼티인 contacts가 가리키던 contacts 인스턴스의 메모리도 같이 해제되는것 아님!!

contacts 인스턴스의 RC가 1 감소하는 것 뿐이다!

profile
개발이 어려운 나를 위한... 개발노트

0개의 댓글