본문 바로가기

 𝗔𝗣𝗣𝗟𝗘/SWIFT : GRAMMAR

Swift 기초 문법 - ARC와 메모리 누수(Automatic Reference Counting)

 

 

 

 

ARC와 메모리 누수(Automatic Reference Counting)

💡 ARC와 메모리 누수(Automatic Reference Counting)

 

 

 

 

 

ARC

정의 : "Automatic Reference Counting"의 약자

기능 : 개발자가 메모리 할당 및 해제를 명시적으로 처리할 필요 없이 객체의 참조를 추적하여 메모리를 자동으로 관리

동작 방식 : 객체의 참조 카운트를 추적, 더 이상 참조되지 않는 객체는 자동으로 메모리에서 해제

목적 :

   메모리 누수를 방지하여 프로그램이 더 이상 필요하지 않은 메모리를 해제하지 않고 남겨두는 상황 예방

주의사항 : 모든 종류의 메모리 누수를 방지해주진 않으며, 순환 참조와 같은 특정 상황에서는 개발자가 약한 참조(Weak Reference)와 비소유 참조(Unowned Reference)를 적절히 사용하여 메모리 누수를 방지해야 함

 

 

 ARC의 작동 방식 

참조 카운팅

   객체의 참조를 추적하여 참조 카운트를 관리

   → 각 객체는 현재 참조하고 있는 다른 객체의 수를 나타내는 카운트를 가지고 있음

참조 증가 및 감소

   → 객체가 새로운 참조를 받으면 해당 객체의 참조 카운트가 증가

    참조가 해제되면 참조 카운트가 감소

참조 해제

   객체의 참조 카운트가 0이 되면 해당 객체는 더 이상 필요하지 않다고 판단되어 메모리에서 자동 해제

순환 참조 처리

   순환 참조를 방지하기 위해 약한 참조(Weak Reference)비소유 참조(Unowned Reference)를 제공

    이를 통해 순환 참조로 인한 메모리 누수를 방지할 수 있음

컴파일러 지원

   컴파일러 수준에서 코드를 분석하여 메모리 관리 보장

    따라서 개발자가 명시적으로 메모리 관리를 할 필요가 없음

성능 최적화

   ARC는 객체의 수명 추적을 효율적으로 수행하여 메모리 관리 오버헤드 최소화

 

 

예시

클래스 Person을 정의하고, 그 안에 초기화(init) 및 해제(deinit) 메시지를 출력

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}

var reference1: Person?
var reference2: Person?
var reference3: Person?

reference1 = Person(name: "John Appleseed") // RC: 1️⃣
// Prints "John Appleseed is being initialized"

reference2 = reference1 // RC: 2️⃣
reference3 = reference1 // RC: 3️⃣

reference1 = nil // RC: 2️⃣
reference2 = nil // RC: 1️⃣

reference3 = nil // RC: 0️⃣
// Prints "John Appleseed is being deinitialized"

 

 

 예시 하나씩 뜯어보기 

Person 클래스는 인스턴스의 name 프로퍼티를 설정, 해당 인스턴스가 초기화됨을 알리는 메시지 출력

소멸자 또한 가지고 있기 때문에 소멸자 실행시에도 클래스 인스턴스가 해제됨을 알리는 메시지 출력

아래 인용된 코드들은 Person? 타입의 변수를 정의

다음 코드들의 새로운 Person 인스턴스를 위한 다수의 참조를 설정하기 위해 사용될 것

// Code(01) | Person? 타입 변수 정의

var reference1: Person?
var reference2: Person?
var reference3: Person?

 

 

위 코드Code(01) 중 하나의 변수에 새로운 Person 클래스 인스턴스를 생성하여 할당할 수 있음

// Code(02) | 새로운 Person 클래스 인스턴스

reference1 = Person(name: "John Appleseed") // RC: 1️⃣
// Prints "John Appleseed is being initialized"

Person클래스의 생성자 호출 시점에 'John Appleseed is being initialized' 가 출력, 이는 새로운 인스턴스 생성이 완료됨을 알림

 새로운 Person인스턴스가 reference1 변수에 할당되었기 때문에 reference1은 새로운 Person 인스턴스에 대한 참조가 생김

 최소한 하나의 강한 참조가 있으므로, ARC는 해당 Person 인스턴스가 메모리에서 유지되며 메모리에서 해제되지 않도록 보장함

 

 

만약 동일한 Person 인스턴스를 2개의 변수에 추가 할당한다면?

해당 인스턴스에 대한 두개의 강한 참조가 추가로 생성됨

// Code(03) | 동일한 Person 인스턴스를 2개의 변수에 추가

reference2 = reference1 // RC: 2️⃣
reference3 = reference1 // RC: 3️⃣

이제 하나의 Person 인스턴스에 대한 총 3개의 강한 참조가 존재

만약, 3개의 강한 참조 중 두 개의 강한 참조를 없애기 위해 두 개의 참조변수에 nil을 할당한다면 1개의 강한참조가 남게 됨

최소 1개의 참조만 남아있음 → Person 인스턴스 메모리에서 해제되지 않음

 

 

// Code(04) | 하나의 Person 인스턴스에 대한 총 3개의 강한 참조가 존재

reference1 = nil // RC: 2️⃣
reference2 = nil // RC: 1️⃣

ARC는 Person 인스턴스에 대한 마지막 1개의 강한 참조가 깨지기(Person 인스턴스를 더이상 사용하지 않게 될 명확한 시점) 전까지는 해당 인스턴스를 해제하지 않음

 

 

 


 

 

메모리 누수

 

- 애플리케이션이 더 이상 필요하지 않은 메모리를 제대로 해제하지 못하여 메모리 사용량이 계속해서 증가하는 상황

- 객체나 리소스가 더 이상 필요하지 않은데도 메모리에서 해제되지 않으면 메모리 누수 발생

 

 

 메모리 누수 원인 

강한 참조 순환 (Strong Reference Cycle)

   두 개 이상의 객체가 서로 강한 참조를 유지하여 순환 구조를 만들 때 발생

    이러한 순환 구조로 인해 객체들이 해제되지 못하고 메모리에 계속해서 남게 됨
클로저에서 강한 참조 (Strong Reference in Closures)

   객체를 강하게 캡처하면서 클로저와 객체 간에 강한 참조 순환을 만들 수 있음

    이로 인해 객체가 해제되지 못하고 메모리에 계속해서 유지됨

 

 

 해결 방법 

약한 참조(Weak Reference)

   순환 참조를 방지하기 위해 객체 간의 관계 중 하나를 약한 참조로 선언

    이렇게 하면 참조하는 객체의 수명이 다른 객체의 수명에 의존하지 않게 되어 순환 참조를 방지할 수 있음

비소유 참조(Unowned Reference)

   약한 참조와 유사하지만, 객체가 해제된 후에도 nil로 설정되지 않음

    객체의 생명주기가 참조하는 객체의 생명주기와 동일하거나 더 길 때 사용

클로저 캡처 리스트(Capture Lists)

   클로저 내에서 발생하는 순환 참조를 방지하기 위해 캡처 리스트를 사용하여 클로저가 강한 참조를 보유하지 않도록 함

    클로저 내에서 self를 캡처할 때 캡처 리스트를 활용하여 약한 참조로 캡처할 수 있음

강한 참조 사이클 해제(Strong Reference Cycle Resolution)

   강한 참조 사이클이 발생했을 때, 객체들 간의 관계를 적절히 변경하여 사이클을 해제할 수 있음

    이를 통해 각 객체가 서로를 강한 참조하지 않도록 조정하여 메모리 누수 방지

수동 해제(Manual Deallocation)

   필요한 경우에는 객체를 수동으로 해제할 수 있음

    이는 특히 순환 참조를 해결하기 어려운 복잡한 상황에서 유용할 수 있음

 

 

예시

강한 참조 순환 해결

class Person {
    var name: String
    var car: Car?

    init(name: String) {
        self.name = name
        print("\(name) is initialized.")
    }

    deinit {
        print("\(name) is deallocated.")
    }
}

class Car {
    weak var owner: Person?

    init() {
        owner = nil
        print("Car is initialized.")
    }

    deinit {
        print("Car is deallocated.")
    }
}

var john: Person? = Person(name: "John")
var car: Car? = Car()

john?.car = car
car?.owner = john

john = nil
car = nil

 

 

 메모리 누수 유형 

순환 참조(Circular References)

   두 개 이상의 객체가 서로를 간접적으로 또는 직접적으로 참조하는 경우 발생

   → 객체들 간의 참조 카운트가 0이 되지 않아 메모리가 해제되지 않음

   → 이를 해결하기 위해 강한 참조를 약한 참조로 변경하거나, 강한 참조를 수동으로 해제해야 함

클로저와 순환 참조(Closures and Retain Cycles)

   클로저 내에서 self를 캡처하고 해당 클로저가 self를 강한 참조로 보유하는 경우 발생

   → 클로저에서 약한 참조로 self를 캡처하거나, 클로저가 더 이상 필요하지 않을 때 적절히 해제해야 함

객체 해제 후에 접근(Object Deallocation and Access)

   객체를 사용하는 동안 해당 객체가 해제되었는지 확인하지 않고 접근하려고 할 때 발생

   → 이는 애플리케이션의 여러 부분에서 발생할 수 있으며, 주로 비동기 작업에서 발생함

 

 

 메모리 누수 유형 표 

유형 설명 해결 방법
순환 참조
(Circular References)
두 개 이상의 객체가 서로를 간접적으로 또는 직접적으로 참조하여 참조 카운트가 0이 되지 않는 경우 발생 - 강한 참조를 약한 참조로 변경
- 강한 참조를 수동으로 해제
클로저와 순환 참조
(Closures and Retain Cycles)
클로저 내에서 self를 캡처하고 해당 클로저가 self를 강한 참조로 보유하는 경우 발생 - 클로저에서 약한 참조로 self를 캡처
- 클로저가 더 이상 필요하지 않을 때 적절히 해제
객체 해제 후에 접근
(Object Deallocation and Access)
객체를 사용하는 동안 해당 객체가 해제되었는지 확인하지 않고 접근하려고 할 때 발생 - 객체에 접근하기 전에 해당 객체가 존재하는지 확인
- 비동기 작업에서 안전하게 객체 접근하기 위해 콜백이나 완료 핸들러를 사용

 

 

 

 

 

 

 

참고

참고 2

Recent Posts
Visits
Today
Yesterday
Archives
Calendar
«   2024/12   »
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 31