본문 바로가기
swift

메모리 관리 이해

by 승환파크 2024. 7. 9.

메모리와 디스크 기본 개념

메모리

  • 일반적으로 RAM 을 말하는 경우가 많다.
  • 맥북에서도 몇 GB 짜리 RAM 을 사용하는지 볼 수 있다.
  • RAM 은 휘발성 메모리이다. 즉, 데이터를 영구적으로 저장하지 않는다. 일시적인 저장에 사용한다.→ 앱도 결국 데이터 덩어리이기 때문에, 실행을 시키면 메모리에 올라간다.→ RAM 의 용량이 클 수록, 동시에 실행시킬 수 있는 앱의 총량이 높아진다고 생각할 수 있다.
  • → 그렇기 때문에 메모리에 저장된 데이터는 앱이 메모리에서 내려올 때 같이 내려오게 되는 것.
  • → 앱 실행중에 메모리에 저장된 데이터들은 앱을 종료하면 함께 삭제된다. (휘발된다)
  • 디스크보다 속도가 빠르다. (CPU 가 디스크보다 메모리에 더 빨리 접근할 수 있다.)
  • 디스크에 비해 용량이 작다. (보통 8GB, 16GB, 32GB)
  • EEPROM 과 같은 비휘발성 메모리도 있다. 아이폰은 이곳에 장치의 일련번호 및 하드웨어 정보를 저장한다.

디스크

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

Reference Counting

 

메모리를 할당 받은 객체를 인스턴스라고 부른다. 예를 들어 아래 코드에서는 myClass 가 인스턴스가 된다.

class MyClass {}

// 메모리를 할당받음. 인스턴스.
let myClass = MyClass()

 

인스턴스는 하나 이상의 참조자(소유자 = owner)가 있어야 메모리에 유지된다. 소유자가 없다면 그 즉시 메모리에서 제거 된다. 이 때 인스턴스를 참조하고 있는 소유자의 개수를 reference count 라고 한다.

 

reference count > 0 이면 메모리가 살아있고, reference count = 0 이면 메모리에서 삭제된다.

그렇기 때문에, 더 이상 사용하지 않을 인스턴스의 reference count 가 0보다 크지 않도록 주의를 해야한다.

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

// RC = 1
var myClass: MyClass? = MyClass()

// RC = 2
var myClass2 = myClass

// RC = 2 - 1 = 1
myClass = nil

// RC = 1 - 1 = 0
myClass2 = nil

 

ARC 와 MRC

ARC = Automatic Reference Counting

MRC = Manual Reference Counting

 

ARC

  • ARC 는 Swift 의 메모리 관리 시스템. Java 에 GC 가 있다면 Swift 에는 ARC 가 있음.
  • Reference Count 를 자동으로 계산. (Automatic)
    • 객체가 생성될 때 RC 가 1 로 설정
    • 객체가 다른 변수나 속성에 할당되어 참조될때마다 RC 가 1 씩 증가
    • 객체에 대한 참조가 해제될때마다 RC 가 감소
    • RC 0 이 되면 더 이상 사용되지 않는 것으로 간주되어 메모리에서 해제.

MRC

  • MRC 는 Objective-C 에서 사용하는 메모리 관리 시스템.
  • Reference Count 를 개발자가 코드로 직접 계산. (Manual)
    • 객체가 생성될때 개발자가 명시적으로 메모리 할당
    • 객체를 다른 변수나 속성에 할당되어 참조될때마다 개발자가 명시적으로 RC 증가
    • 객체에 대한 참조가 해제될때마다 개발자가 명시적으로 RC 감소
    • RC 가 0 이되면 개발자가 명시적으로 메모리에서 해제.

 

ARC는 자동으로 RC 카운트를 해서 메모리 관리를 해주는 좋은 시스템이나, ARC로 잡아내지 못하는 메모리 누수 상황이 발생할 수 있기 때문에 메모리 관리 방법을 반드시 알아야 한다.

 

약참조와 강참조

약참조

  • Reference Count 를 증가시키지 않으면서 참조하는 것.
  • weak 키워드를 붙여서 약참조를 할 수 있다.

강참조

  • Reference Count 를 증가시키면서 참조하는 것.
  • 일반적인 참조 방식을 말한다.

 

클로저의 캡처링 기능

Swift 의 클로저 안에서 값을 사용하는 방법 중에는 캡처링이 있다.

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

// people RC = 1
var people: People? = People()

// 클로저 내부에서 people 캡쳐, RC 1증가. people RC -> 1 + 1 = 2
let printMBTI: () -> () = {[people] in
    guard let people else { return }
    print("people's MBTI = \(people.mbti)")
}

printMBTI()

// people RC -> 2 - 1 = 1
people = nil

 

위 코드를 보면 printMBTI 라는 클로저를 선언했고, 클로저 내부에서 클로저 외부의 값을 people 이라는 객체를 가져다 쓰고 싶으면 값을 캡처해야 한다. 이 때 [ ] 로 감싸면 값을 캡처링해서 클로저 내부에서 값을 사용할 수 있게된다.

 

클로저 내부에서 값을 캡처하면 RC가 1 증가한다. 그렇기 때문에 위 예시에서는 people 의 deinit 소멸자가 호출되지 않게된다.

 

위 코드를 개선하여 메모리 누수가 발생하지 않는 상황을 만드려면 아래 코드처럼 작성해야 발생하지 않는다.

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

// people RC = 1
var people: People? = People()

// weak 키워드로 약참조를 하여 RC가 증가하지 않음
let printMBTI: () -> () = {[weak people] in
    guard let people else { return }
    print("people's MBTI = \(people.mbti)")
}

printMBTI()

// people RC -> 1 - 1 = 0
people = nil

 

위 코드를 실행시키면 deinit 의 "클래스 소멸" 이 호출되는 것을 확인할 수 있다.

 

순환참조

 

순환 참조는 위 사진과 같이 A가 B를 참조하고, B가 A를 참조해서 서로가 서로를 참조하는 상태를 순환 참조라고 한다.

일반적으로 순환 참조는 메모리 누수를 발생시키는 대표적인 사례이다.

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

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

// person RC = 1
var person: Person? = Person()
// dog RC = 1
var dog: Dog? = Dog()

// dog RC = 2
person?.pet = dog
// person RC = 2
dog?.owner = person

// person RC = 1
person = nil
// dog RC = 1
dog = nil

 

모든 코드는 GitHub 에서 확인할 수 있다.

◎메모리 관리 이해 코드 보러가기

 

iOS-Proficiency-Week/ReferenceCount.playground at main · sh990920/iOS-Proficiency-Week

iOS 앱개발 숙련주차 블로그 정리 자료. Contribute to sh990920/iOS-Proficiency-Week development by creating an account on GitHub.

github.com

'swift' 카테고리의 다른 글

Swift Delegate 패턴  (0) 2024.07.09
CoreData 와 UserDefaults  (0) 2024.07.09
UIViewController 생명주기  (3) 2024.07.09
UISegmentedControl  (0) 2024.07.03
UIScrollView  (0) 2024.06.21