2023년 1월 1일
08:00 AM
Buffering ...

최근 글 👑

[Swift-Study] 심화 문법종합반 2주차 4일차 정리 - ARC와 메모리 누수

2024. 3. 26. 20:13ㆍIOS/Swift-Study
SMALL

ARC와 메모리 누수

메모리를 관리해주는 ARC와 메모리 누수를

해결하는 방법에 대한 내용을 정리해봤습니다.

ARC(Automatic Reference Counting)

Swift의 ARC(Automatic Reference Counting)는 메모리 관리 기법 중 하나로,

객체나 인스턴스가 참조되는 횟수를 추적하여 메모리에서 해제할 시점을 결정하고

객체가 생성될 때마다 참조 횟수가 1 증가하고, 해당 객체를 참조하는 다른 객체나 변수가 없어지거나

더 이상 사용되지 않을 때 참조 횟수가 1 감소.

 

참조 횟수가 0이 되면 해당 객체는 메모리에서 해제

 

ARC의 작동 방식
1 2 3 4
객체 생성 객체 참조 참조 해제 Zeroing Weak References
객체가 생성되면
참조 횟수가 1 증가
객체를 다른 변수나 상수에
할당하면 해당 객체의
참조 횟수가 1 증가
객체의 참조가 없어지면
(참조하는 변수나 상수가
없거나 **nil**이 할당되면) 참조 횟수가 1 감소
약한 참조
(Weak Reference)는
객체의 참조 횟수를
증가시키지 않고 추적, 
객체가 해제되면 약한 참조는 자동으로 **nil**로 설정
class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    deinit { // deinit은 Person 인스턴스가 메모리에서 해제될 때 그 때 직전에 불리게 되는 함수입니다.
    // 즉, deinit은 인스턴스가 메모리에서 해제될 때 사용하는 
        print("\(name) is being deinitialized")
    }
}

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

reference1 = Person(name: "Jun") // RC: 1
/*
 Person의 이름이 Jun 이라는 이름의 인스턴스를 생성하고 
 reference1에 담게 되면, 레퍼런스 카운팅이 1이 증가합니다. (객체를 생성 하였기 때문입니다.)
 다른변수에 참조를 할당 하는 경우 레퍼런스 카운터가 하나 더 증가 됩니다.
 즉, Person으로 만들어진 인스턴스는 reference1도 Person을 바라 보고 있으며,
 reference2와 3도 Person을 바라보고 있는 것 입니다.
 
 이때 reference1에 nil을 할당 하면 그 순간 레퍼런스 카운팅이 -1이 적용이 되어 1이 감소합니다.
 레퍼런스가 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 "Jun is being deinitialized"

 

강한 참조 순환(Strong Reference Cycle)문제와 해결 방법

Swift로 개발할 때에는 메모리 누수(Memory Leak)을 주의!

참조는 디폴트로 강한 참조(Strong Reference)를 사용하는데,

이 강한 참조를 잘못 사용하면 메모리 누수(Memory Leak) 문제가 발생

 

[ 가장 대표적인 예 ]

두 개 이상의 인스턴스가 서로가 서로를 강한 참조일 때 발생

이 문제를 강한 참조 순환(Strong Reference Cycle or Retain Cycle)이라고 부릅니다.

강한 참조 순환 : 메모리가 해제되지 않고 유지되어 메모리 누수가 발생하는 현상

// 강한 참조 순환 문제의 예시
class Man {
    var name: String
    var girlfriend: Woman? // 옵셔널

    init(name: String) {
        self.name = name
    }
    deinit { print("Man Deinit!") } // 인스턴스가 해제되기 직전에 불림
}

class Woman {
    var name: String
    var boyfriend: Man? // 옵셔널

    init(name: String) {
        self.name = name
    }
    deinit { print("Woman Deinit!") } // 인스턴스가 해제되기 직전에 불림
}
/*
 아래 코드에서는 인스턴스를 생성하고 있습니다.
 옵셔널 Man 타입으로 철수라는 사람을 만들고 있으며,
 옵셔널 Woman 타입으로 영희 라는 사람을 만들고 있습니다.
 그리고, 철수의 여자친구로 영희를 넣어주고 있고 영희의 남자친구로 철수를 넣어주고 있습니다.
 이 상태에서 둘의 인스턴스의 메모리를 해제하고자 합니다. 
 즉, 영희와 철수를 프로그램상에서 사용 하지 않으려고 하는 것입니다.
 그래서 철수와 영희에게 각각 nil 값을 넣어주었는데... 두 객체는 해제되지 않습니다.
 그 이유는 객체를 생성할 때 레퍼런스 카운터가 1씩 증가하는데 그 참조값을 철수와 영희는 가지고 있습니다.
 이 때 다른 변수에 재할당이 되었을 때 레퍼런스 카운터가 하나씩 증가해서 아래의 두개의 인스턴스는 각각의
 레퍼런스 카운터가 2씩 됩니다. 그렇기 때문에 각각의 인스턴스의 nil을 넣는다고 해도 1개가 줄기 때문에
 1이 남아있는 상태가 되어 deinit을 호출하지 못하는 것입니다.
*/
// 인스턴스 생성
var chelosu: Man? = .init(name: "철수") // RC 1
var yeonghee: Woman? = .init(name: "영희") // RC 1

chelosu?.girlfriend = yeonghee // 재할당 -> RC 2
yeonghee?.boyfriend = chelosu // 재할당 -> RC 2

chelosu = nil
yeonghee = nil

 

강한 참조 순환 문제 해결 방법

약한 참조(Weak Reference)

- 참조되는 대상을 약하게 참조하여 순환 참조를 방지

- 참조하는 객체를 강제로 유지하지 않고, 참조 대상이 메모리에서 해제되면 자동으로 **nil**로 설정

- 두 객체가 서로를 강하게 참조하는 경우, 순환 참조로 인해 메모리 누수가 발생가능

이런 상황에서는 한쪽을 **weak**로 선언하여 순환 참조 문제를 해결

- 주로 순환 참조를 방지하기 위해 사용

 

**weak**

옵셔널로 선언되는 참조

 

비소유 참조(Unowned Reference)

참조되는 대상이 항상 유효한 경우에만 사용하며, 해당 대상이 해제될 수 있는 상황에는 사용금지

 

**unowned**

옵셔널이 아닌 비소유 참조

항상 값이 있다고 가정, 참조하는 객체가 해제되면 런타임 에러가 발생가능

 

unowned

- 참조는 참조 대상이 해제될 수 있는 경우에만 사용

해당하는 객체가 메모리에서 해제되지 않은 상태에서만 해당 unowned 참조를 사용

// 약한 참조(Weak Reference)를 사용한 해결 방법
/*
 Man이나 Woman 두쪽중 한쪽에만 weak를 사용해주면 됩니다.
 weak를 써주는 동안 레퍼런스카운터를 올리지 않습니다.
*/
class Man {
    var name: String
    weak var girlfriend: Woman? 

    init(name: String) {
        self.name = name
    }
    deinit { print("Man Deinit!") }
}

class Woman {
    var name: String
    var boyfriend: Man?

    init(name: String) {
        self.name = name
    }
    deinit { print("Woman Deinit!") }
}


var chelosu: Man? = .init(name: "철수")
var yeonghee: Woman? = .init(name: "영희")

chelosu?.girlfriend = yeonghee
yeonghee?.boyfriend = chelosu


chelosu = nil
yeonghee = nil
chelosu?.girlfriend // nil
// 비소유 참조(Unowned Reference)를 사용하여 강한 참조 순환 문제를 해결
// unowned 사용
class Man {
    var name: String
    unowned var girlfriend: Woman?

    init(name: String) {
        self.name = name
    }
    deinit { print("Man Deinit!") }
}

class Woman {
    var name: String
    var boyfriend: Man?

    init(name: String) {
        self.name = name
    }
    deinit { print("Woman Deinit!") }
}

var chelosu: Man? = .init(name: "철수")
var yeonghee: Woman? = .init(name: "영희")

chelosu?.girlfriend = yeonghee
yeonghee?.boyfriend = chelosu


yeonghee = nil
chelosu?.girlfriend // 에러 발생
728x90