메모리 누수(Memory Leak)와 강한 참조 사이클(Strong Reference Cycle)

썹스·2022년 11월 17일
0

Swift 문법

목록 보기
40/68

메모리 누수(Memory Leak)

메모리 누수(Memory Leak) 현상은 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 현상입니다.

이처럼 메모리 누수 현상이 지속되면 메모리(Heap) 공간이 꽉 차게 되어 프로그램이 다운되는 현상이 발생할 수도 있습니다.

Swift에서 메모리(Heap) 공간의 관리는 ARC(Automatic Reference Counting) 모델에 의해 자동으로 관리되지만, 강한 참조 사이클(Strong Reference Cycle)로 정의된 데이터는 카운트 값이 계속 유지될 수 있기 때문에 주의해야 합니다.



📌 강한 참조 사이클(Strong Reference Cycle)에 의한 메모리 누수(Memory Leak)

Swift에서 ARC는 컴파일러에 의해 자동으로 작동됩니다.

하지만 강한 참조 사이클에 의해 데이터가 메모리(힙)에 제거되지 않고 계속 남는 현상이 발생해서 메모리 누수가 발생하는 현상이 일어날 수 있습니다.

⚙️ 강한 참조

인스턴스(객체) 또는 클로저가 한쪽으로 인스턴스(객체) 또는 클로저를 지목하여 참조 (단방향 참조)

⚙️ 강한 참조 사이클

인스턴스(객체) 또는 클로저가 서로(쌍방향)를 지목하여 참조

✅ 강한 참조 사이클에 의한 메모리 누수 사례

class Boy{
    var girlfriendName: Girl?

    deinit{  // 소멸자를 사용하여 참조 카운트 값이 0이 되었을 때 작동
        print("인스턴스(객체)의 참조 카운트 값이 0이 되어 해당 데이터가 메모리(Heap)에서 제거되었습니다.")
    }
}

class Girl{
    var boyfriendName: Boy?

    deinit{  // 소멸자를 사용하여 참조 카운트 값이 0이 되었을 때 작동
        print("인스턴스(객체)의 참조 카운트 값이 0이 되어 해당 데이터가 메모리(Heap)에서 제거되었습니다.")
    }
}


var kim: Boy? = Boy()       // kim 참조 카운트 1 증가
var lee: Girl? = Girl()     // lee 참조 카운트 1 증가


// 각각의 인스턴스(객체)가 서로를 참조
kim?.girlfriendName = lee   // kim 참조 카운트 1 증가 -> kim의 총 카운트 = 2
lee?.boyfriendName = kim    // lee 참조 카운트 1 증가 -> lee의 총 카운트 = 2


// 서로 참조를 했기 때문에 인스턴스(객체)를 nil로 초기화해도 완벽하게 참조 카운트 값을 0으로 만들 수 없음
kim = nil   // kim 참조 카운트 1 감소 -> kim의 총 카운트 = 1
lee = nil   // lee 참조 카운트 1 감소 -> lee의 총 카운트 = 1

📌 메모리 누수(Memory Leak) 해결 방법

강한 참조 사이클에 의한 메모리 누수를 해결/예방하는 방법은 크게 3가지로 나뉩니다.

1️⃣ 강한 참조 사이클이 일어날 코드를 작성하지 않는다. (추천 X)
2️⃣ 약한 참조(Weak Reference)를 활용하여 코드를 작성한다.
3️⃣ 비소유/무소유 참조(Unowned Reference)를 활용하여 코드를 작성한다.

✅ 약한 참조(Weak Reference)

  • 약한 참조는 서로를 가리키는 인스턴스(객체)의 카운트 결과를 세지 않는 방식입니다. (비소유/무소유 참조와 같은 개념)

  • 약한 참조는 소유자(상위 인스턴스)보다 짧은 생명주기를 가진 인스턴스를 참조할 때 주로 사용합니다.

  • 참조하고 있던 인스턴스가 메모리에서 제거되면, 참조했던 다른 한쪽의 인스턴스는 nil로 초기화됩니다.

  • 약한 참조는 변수(var)로만 정의할 수 있고, 옵셔널 타입으로만 정의해야 합니다.

  • 약한 참조로 사용할 변수를 선언하기 위해서는 변수 앞에 weak 키워드를 작성해야 합니다.
    weak var 변수명: 옵셔널 타입
class Boy{
    weak var girlfriendName: Girl?  // 약한 참조 타입으로 변수 생성

    deinit{  // 소멸자를 사용하여 참조 카운팅이 0이 되었을 때 작동
        print("인스턴스(객체)의 참조 카운팅이 0이 되어 해당 데이터가 메모리(Heap)에서 제거되었습니다.")
    }
}

class Girl{
    weak var boyfriendName: Boy?  // 약한 참조 타입으로 변수 생성

    deinit{  // 소멸자를 사용하여 참조 카운팅이 0이 되었을 때 작동
        print("인스턴스(객체)의 참조 카운팅이 0이 되어 해당 데이터가 메모리(Heap)에서 제거되었습니다.")
    }
}


var kim: Boy? = Boy()       // kim 참조 카운팅 1 증가
var lee: Girl? = Girl()     // lee 참조 카운팅 1 증가


// 각각의 인스턴스(객체)가 서로를 참조
kim?.girlfriendName = lee   // 약한 참조에 의해 카운팅 X -> kim의 총 카운팅 = 1
lee?.boyfriendName = kim    // 약한 참조에 의해 카운팅 X -> lee의 총 카운팅 = 1


// 약한 참조에 의해 참조 카운트 값을 0으로 만들 수 있음
kim = nil   // kim 참조 카운팅 1 감소 -> kim의 총 카운팅 = 0
print(lee?.boyfriendName)  // 약한 참조에 의해 lee?.boyfriendName의 값이 nil로 초기화
lee = nil   // lee 참조 카운팅 1 감소 -> lee의 총 카운팅 = 0


/*
출력 결과
인스턴스(객체)의 참조 카운팅이 0이 되어 해당 데이터가 메모리(Heap)에서 제거되었습니다.
nil
인스턴스(객체)의 참조 카운팅이 0이 되어 해당 데이터가 메모리(Heap)에서 제거되었습니다.
*/

✅ 비소유/무소유 참조(Unowned Reference)

  • 비소유/무소유 참조는 서로를 가리키는 인스턴스(객체)의 카운트 결과를 세지 않는 방식입니다. (약한 참조와 같은 개념)

  • 비소유/무소유 참조는 소유자(상위 인스턴스)보다 길거나 같은 생명주기를 가진 인스턴스를 참조할 때 주로 사용합니다.

  • 참조하고 있던 인스턴스가 메모리에서 제거되면, 참조했던 다른 한쪽의 인스턴스는 nil로 초기화되지 않습니다.

  • 비소유/무소유 참조는 변수(var), 상수(let) 둘 다 정의할 수 있고, 다양한 타입으로 정의할 수 있습니다.

  • 비소유/무소유 참조로 사용할 변수(상수)를 선언하기 위해서는 변수(상수) 앞에 unowned 키워드를 작성해야 합니다.
    unowned var 변수명: 다양한 타입
class Boy{
    unowned var girlfriendName: Girl?

    deinit{  // 소멸자를 사용하여 참조 카운팅이 0이 되었을 때 작동
        print("인스턴스(객체)의 참조 카운팅이 0이 되어 해당 데이터가 메모리(Heap)에서 제거되었습니다.")
    }
}

class Girl{
    unowned var boyfriendName: Boy?

    deinit{  // 소멸자를 사용하여 참조 카운팅이 0이 되었을 때 작동
        print("인스턴스(객체)의 참조 카운팅이 0이 되어 해당 데이터가 메모리(Heap)에서 제거되었습니다.")
    }
}


var kim: Boy? = Boy()       // kim 참조 카운팅 1 증가
var lee: Girl? = Girl()     // lee 참조 카운팅 1 증가


// 각각의 인스턴스(객체)가 서로를 참조
kim?.girlfriendName = lee   // 비소유/무소유 참조에 의해 카운팅 X -> kim의 총 카운팅 = 1
lee?.boyfriendName = kim    // 비소유/무소유 참조에 의해 카운팅 X -> lee의 총 카운팅 = 1


// 비소유/무소유 참조에 의해 참조 카운트 값을 0으로 만들 수 있음
kim = nil   // kim 참조 카운팅 1 감소 -> kim의 총 카운팅 = 0
lee = nil   // lee 참조 카운팅 1 감소 -> lee의 총 카운팅 = 0


/*
출력 결과
인스턴스(객체)의 참조 카운팅이 0이 되어 해당 데이터가 메모리(Heap)에서 제거되었습니다.
인스턴스(객체)의 참조 카운팅이 0이 되어 해당 데이터가 메모리(Heap)에서 제거되었습니다.
*/

📌 참조 타입(Reference Type)에서 발생하는 강한 참조 사이클(Strong Reference Cycle)에 의한 메모리 누수(Memory Leak) 및 해결법

참조 타입의 캡처 리스트 - 강한 참조 사이클의 문제 해결

profile
응애 나 코린이(비트코인X 코딩O)

0개의 댓글