메모리 관리 이유와 ARC

Jee.e (황지희)·2022년 4월 27일
0
post-custom-banner

메모리는 왜 관리해야하는가?

가장 단순한 메모리 관리 방법은 프로그램의 요청이 있을 때만 메모리를 할당하고, 필요하지 않을때 할당을 해제하는 것이다.
이렇게 메모리를 관리하는 이유는, 멀티 프로그래밍 환경에서 한정된 메모리를 더 효율적으로 이용할 수 있도록 하기 위함이다.



✅ Swift의 메모리 관리 방법?

Swift는 ARC(Automatic Reference Counting)를 통해 메모리를 관리한다.



ARC?

Automatic Reference Counting 자동 참조 카운팅

  • 자동으로 메모리를 관리해주는 방식이다.
  • 더이상 필요하지 않은 class의 인스턴스를 메모리에서 해제하는 방식으로 동작한다.
  • 참조 타입인 Class의 인스턴스에만 적용 (구조체, 열거형은 참조하지 않기때문에 관리할 필요 X)
  • 클래스의 인스턴스를 생성할 때마다 ARC는 메모리 공간을 따로 할당한다.
    (메모리에는 인스턴스 타입 / 인스턴스 프로퍼티 값 등 저장) → 인스턴스가 더이상 필요 없어지면, 메모리를 다른 곳에서 사용하도록 ARC가 메모리에서 인스턴스를 제거한다.
  • 클래스의 인스턴스가 호출될 때마다 참조 횟수가 +1, nil 을 할당할 때마다 참조 횟수가 -1이 되는데 최종적으로 참조 횟수가 0이 되면 메모리에서 해제된다.


참조의 기본은 strong(강한)참조다.
근데, 강한 참조 시 영구적으로 메모리가 해제되지 않는 순환 참조 가 발생될 수 있다.

✅ 순환 참조가 일어날 수 있는 상황?

아래와 같이 두 클래스가 존재한다고 하자.
이때, 두 클래스의 프로퍼티가 서로 다른 클래스를 참조하고 있다.

class Student {
    let name: String
    var school: **School?**
    
    init(name: String) {
        self.name = name
    }
}

class School {
    let schoolName: String
    var student: **Student?**
    
    init(schoolName: String) {
        self.schoolName = schoolName
    }
}

순환 참조는 아래와 같은 상황에 발생하게 된다.

let firstStudent = Student(name: "Jee")
let appleUniversity = School(schoolName: "애플대학교")

firstStudent.school = appleUniversity

Studentschool 프로퍼티에 School 인스턴스의 주소값을 할당하는 것이다.
이렇게 서로 다른 객체가 서로를 참조하고 있는 형태를 순환 참조 라고 한다.


✅ 순환 참조의 문제점?

앞서 말한대로 nil 을 할당하면 참조 횟수가 -1이 되어야 한다.
근데, 이렇게 서로를 참조하는 순환 참조가 발생하게 되면 메모리 해제가 되지않고 계속 메모리에 할당된 상태로 남아있게된다.
즉, memory leak(메모리 누수 현상) 이 계속 발생하게 된다.


✅ 위 문제점을 해결하기 위해선? 약한 참조와 미소유 참조!

  • weak
    • 프로퍼티 앞에weak 키워드를 붙여 선언을 하게 되면, 인스턴스 참조 시 참조 횟수를 증가시키지 않는다.
    • 가리키던 인스턴스의 메모리 해제 시 nil 이 할당되므로, 항상 변수 + 옵셔널 타입이어야 한다.
  • unowned
    • 역시 참조 횟수를 증가시키지 않는다.
    • 약한 참조와 다른 점은, 참조하던 인스턴스가 메모리에서 해제되어도 해제된 메모리 주소값을 계속 갖고있는다.
    • 그래서 메모리 해제된 인스턴스에 접근하려 할 때, 에러를 발생시킨다.

근데 왜 unowned 을 써..?

신용카드에는 명의자가 꼭 있어야 하는 것 처럼, 반드시 존재해야하는 이유가 있을 때 사용한다.

class Person {
  let name: String
  
  // 카드를 소지할 수도, 소지하지 않을 수도 있기 때문에 옵셔널로 정의합니다.
  // 또, 카드를 한 번 가진 후 잃어버리면 안 되기 때문에 강한참조를 해야 합니다.
  var card: CreditCard?
  
  init(name: String) {
    self.name = name
  }
  
  deinit {
    print("\(name) is being deinitialized")
  }
}

class CreditCard {
  let number: UInt
  unowned let owner: Person // 카드는 소유자가 분명히 존재해야 합니다.
  
  init(number: UInt, owner: Person) {
    self.number = number
    self.owner = owner
  }
  
  deinit {
    print("Card #\(number) is being deinitailized")
  }
}

var jee: Person? = Person(name: "Jee") // Person 인스턴스의 참조 횟수: 1

if let person: Person = jee {
  // CreditCard 인스턴스의 참조 횟수: 1
  person.card = CreditCard(number: 1004, owner: person)
  // Person 인스턴스의 참조 횟수: 1
}

jee = nil // Person 인스턴스의 참조 횟수: 0
// CreditCard 인스턴스의 참조 횟수: 0
// Jee is being deinitialized
// Card #1004 is being deinitialized



참고 문서
1. https://babbab2.tistory.com/27
2. 야곰 책

profile
교훈없는 경험은 없다고 생각하는 2년차 iOS 개발자입니다.
post-custom-banner

0개의 댓글