[iOS] ARC 순환참조 해제 문제

TIL 15일 차 - Swift 문법 ARC 순환참조 해제 문제

  • 클래스 A, B 사이에 순환참조가 발생하도록 구현해 주세요.
    • 각 클래스에 deinit  정의하여, 메모리 해제 여부를 확인할 수 있도록 해주세요.

먼저 서로가 서로를 강하게 붙잡고 있어(Strong Reference), 아무도 메모리에서 사라지지 못하는 상황을 만들어야 한다.

 

1. 클래스 간 순환: ClassA는 ClassB를 가지고, ClassB는 ClassA를 가진다.

// 1. 클래스 정의
class A {
    var b: B? // A가 B를 참조함 (기본값은 Strong)
    
    deinit {
        print("A 메모리 해제 됨 (Deinit)")
    }
}

class B {
    var a: A? // B가 A를 참조함 (서로가 서로를 참조 중 -> 순환 참조 1)

    deinit {
        print("B 메모리 해제 됨 (Deinit)")
    }
}

var a: A? = A()
var b: B? = B()

a?.b = b
b?.a = a

a = nil // nil 값을 대입해서 메모리 해제를 시험함
b = nil // 이러한 이유로 앞에서 옵셔널로 선언

// 아무것도 출력되지 않음
// 서로가 서로를 인질로 잡아 deinit이 호출되지 않음
A ──strong──▶ B
B ──strong──▶ A

 

 

weak 참조 특징

- 참조는 하지만 소유하지 않음

- 그래서 상대가 사라지면 -> 자동으로 nil

- 따라서 옵셔널 이어야 함

import UIKit

// 해결 방법: 둘 중에 하나가 약하게 잡으면 됨
class A {
    var b: B? // A가 B를 참조함 (기본값은 Strong)
    
    deinit {
        print("A 메모리 해제 됨 (Deinit)")
    }
}

class B {
    weak var a: A? // B가 A를 약하게 참조함 🛠️

    deinit {
        print("B 메모리 해제 됨 (Deinit)")
    }
}

var a: A? = A()
var b: B? = B()

a?.b = b
b?.a = a

a = nil
b = nil

// A 메모리 해제 됨 (Deinit)
// B 메모리 해제 됨 (Deinit)

 

 

2. 클로저 순환: ClassB의 클로저가 ClassA를 사용하며 또 한 번 강하게 붙잡음

  • 또한 클래스 B 에는 closure: (() -> Void)? 프로퍼티를 만들고, 클로저 내부에서 A의 인스턴스를 참조하게 하여 클로저 기반의 순환 참조도 발생시켜 보세요.

이 케이스는 실무에서도 제일 많이 터지는 경우 중 하나라고 한다.

class A {

    deinit {
        print("A 메모리 해제 됨 (Deinit)")
    }
}

class B {
    var a: A?
    var closure: (() -> Void)?
    deinit {
        print("B 메모리 해제 됨 (Deinit)")
    }
}

var a: A? = A()
var b: B? = B()

b?.a = a // 이 케이스는 순환참조가 아님

b?.closure = {
    print(a!) // A를 강하게 캡쳐
}

a = nil
b = nil

 

이게 순환 참조인 이유는

B ──strong──▶ closure
closure ──strong──▶ A

클로저는 기본적으로 strong capture임
그래서 A를 놓지 않아 deinit 불가

 

 

  • 순환 참조를 해결할 수 있도록 weak, unowned 키워드를 클로저 캡처 리스트를 적절히 사용하여 순환 참조를 해결해 주세요.
b?.closure = { [weak a] in // 이 클로저 안에서는 a를 약하게 잡겠다는 뜻
    guard let a else { return }
    print(a) // A를 weak 캡쳐
}

// 순환참조 문제 해결

 

 

 

그렇다면 이렇게 비슷해 보이는 weak과 unowned의 차이점은 무엇이냐

weak unowned
옵셔널 옵셔널 X
안전 위험 (이미 해제되면 크래시)
언제나 사용 가능 절대 먼저 안 죽는 게 보장될 때만
성능 느림 성능 빠름

 

일단 초보자 단계에서는 weak만 써도 충분하다고 기억해 주면 편하다.

 

 

해결 방법 정리

- 참조 중 하나를 weak / unowned

- 클로저 캡처 리스트에 weak / unowned