메모리 관리

JG Ahn·2024년 12월 3일
0

iOS

목록 보기
9/32
post-thumbnail

메모리 vs 디스크

메모리

  • 일반적으로RAM을 지칭
  • 휘발성이기 때문에 일시적으로 저장할 때 사용
    • 예외로 EEPROM이라는 비휘발성 메모리도 있음.(아이폰에서 장치 일련번호, 하드웨어 정보 저장)
  • 디스크보다 속도가 빠르다 (CPU의 메모리에 대한 접근 속도)

위의 특성들 때문에 주로 실시간 작업처리, 임시작업을 하는 데이터를 저장하는 공간으로 사용

디스크

  • 비휘발성 장치. 영구적인 데이터 저장
  • 파일, 문서, 프로그램 등 용량이 큰 정보를 담음
  • 메모리에 비해 속도가 느리다 (CPU의 디스크에 대한 접근 속도)
  • UserDefaults, CoreData를 활용해 디스크에 데이터 저장

Garbage Collector (GC)

  • Java에서 사용되는 메모리 관리 시스템

RAM은 용량이 상대적으로 크지 않기 때문에 처리할 수 있는 데이터의 양이 한정적이기 때문에 사용하지 않는 데이터가 계속 쌓이면 메모리 부담 증가로 메모리 누수(Memory Leak) 발생.
Java에서 GC는 자동으로 RAM을 효율적으로 사용하기 위해 RAM에서 사용하지 않는 데이터를 해제시키는 일을 함.

Reference Counting

인스턴스(메모리를 할당 받은 객체)는 하나 이상의 참조자(소유자)가 있어야 메모리에 유지된다. 소유자가 없다면 메모리에서 제거된다. 이때 소유자의 개수를 reference count라고 한다.

import UIKit

class MyClass {
    init() {
        print("MyClass 생성")
    }
    
    deinit {
        print("MyClass 소멸")
    }
}

//Reference Count = 1    메모리 할당 받음
var myClass: MyClass? = MyClass()

//Reference Count = 2    myClass2가 myClass를 참조(소유)하면서 카운트 증가
var myClass2 = myClass

//Reference Count = 2 - 1 = 1
myClass = nil

//Reference Count = 1 - 1 = 0
myClass2 = nil //참조 카운트 명시적으로 삭제

위 코드에서 만약 myClass2를 명시적으로 삭제하지 않는다면 메모리를 계속 차지하게 되기 때문에 메모리 누수 발생

ARC & MRC

ARC : Automatic Reference Counting (자동 참조 카운팅)

swift의 메모리 관리 시스템. Reference Count를 자동으로 계산.

  • 객체 생성시 RC = 1
  • 객체가 참조되면 RC += 1
  • 객체 참조 해제시 RC -= 1
  • RC가 0이 되면 메모리 해제

MRC : Manual Reference Counting (수동 참조 카운팅)

Objective-C의 메모리 관리 시스템

  • ARC의 RC를 자동으로 카운팅 하는 기능을 모두 개발자가 직접 명시해야함.

💡ARC가 편리하지만 만능이 아니기 때문에 메모리 누수가 발생할 수 있다. (메모리 방법을 알아야 하는 이유)

클로저의 캡처링

class Practice {
    let mbti = "ISFJ"
    
    init() {
        print("클래스 생성")
    }
    
    deinit {
        print("클래스 소멸")
    }
    
}

//RC = 1
var practice: Practice? = Practice()

//클로저 내부에서 practice 캡처 => RC +1 / RC = 2
let printMbti: () -> () = { [practice] in //캡처링으로 Reference Count 1 증가
    guard let practice else { return }
    print("practice's mbti = \(practice.mbti)")
}

printMbti()

practice = nil

결과

클래스 생성
practice's mbti = ISFJ

클로저를 선언하고 객체를 가져다 쓰려면 캡처를 해야한다. 위 코드에서 printMbti클로저는 클로저 외부에 있는 practice 객체를 사용하기위해 []로 감싸 캡처링을 했다. 캡처를 하면 Reference Count가 증가한다. practice의 참조 카운트를 명시적으로 삭제했지만 클로저의 캡처에서 증가한 참조 카운트가 해제되지 않아 deinit이 출력되지 않았다.(Memory Leak)

해결 방법

약한 참조(weak)를 사용하면 참조 카운트를 증가시키않고 참조하기 때문에 메모리 누수를 방지할 수 있다.

//약한참조 사용
let printMbti: () -> () = { [weak practice] in //약한참조(weak) 사용으로 RC가 증가하지 않도록 함
    guard let practice else { return }
    print("practice's mbti = \(practice.mbti)")
}

printMbti()

practice = nil

결과

클래스 생성
practice's mbti = ISFJ
클래스 소멸

순환 참조 (Circular Reference)


(A→B), (B→A)
위 그림처럼 A가 B를, B가 A를 서로 참조하는 상황을 순환참조라고 한다.

class Person {
    var pat: Dog?
    init() {
        print("클래스 생성")
    }
    
    deinit {
        print("클래스 소멸")
    }
}

class Dog {
    var owner: Person?
    init() {
        print("클래스 생성")
    }
    
    deinit {
        print("클래스 소멸")
    }
}

// person rc = 1
var person: Person? = Person() //dog 참조

// dog rc = 1
var dog: Dog? = Dog() //person 참조

// dog rc = 2
person?.pat = dog

// person rc = 2
dog?.owner = person

// person rc = 2 - 1 = 1
person = nil

// dog rc = 2 - 1 = 1
dog = nil

결과

클래스 생성
클래스 생성

해결 방법

설계할 때 순환참조되는 구조를 피하거나 weak var pat: Dog? 또는 weak var owner: Person? 와 같이 최소 한쪽만이라도 약한참조를 만들어주면 메모리 누수를 막을 수 있다.

0개의 댓글