4T2F / ThinkBig

🌟씽크빅 스터디🌟
5 stars 1 forks source link

Swift에서의 메모리 관리 방법(ARC)에 대해 설명하고, 순환 참조(Circular Reference)를 방지하기 위한 전략은 무엇인가요? #6

Open hamfan524 opened 11 months ago

hamfan524 commented 11 months ago

Swift에서의 메모리 관리 방법(ARC)에 대해 설명하고, 순환 참조(Circular Reference)를 방지하기 위한 전략은 무엇인가요?

hamfan524 commented 11 months ago

ARC

ARC- 자동 참조 계산 (Automatic Reference Counting)는 앱의 메모리 사용량을 자동으로 관리하는 데 사용되는 메모리 관리 기술입니다.
ARC는 앱의 메모리 사용량을 추적하고 관리하여 클래스 인스턴스가 더 이상 필요하지 않을 때 할당이 취소되도록 합니다.

자세히 알아보기

순환 참조 방지 전략

순환 참조는 두 클래스 인스턴스가 서로에 대한 강력한 참조를 보유하여 강력한 참조 순환을 생성할 때 발생합니다.
순환 참조가 문제가 되는 이유는 위에서 설명한 ARC 를 이용하여 참조 카운트가 0 이 되면 메모리에서 할당 해제를 하고 있는데,
강한 순환 참조가 생기게 되면 참조 카운트가 0 이 되지 않아 실제로 사용을 하지 않는 인스턴스가 메모리를 차지하고 있는 메모리 누수 현상이 발생하기 때문입니다.

Swift 에서 Reference Type 의 인스턴스를 참조하는 방식은 Strong, weak, unowned 3가지가 있습니다.

  1. strong (강한 참조)

    • 참조하는 인스턴스의 참조 카운트를 증가시킵니다.
    • 인스턴스를 참조하는 default 방식 (키워드를 생략하게 되면 strong 참조)
  2. weak reference

    • 참조하는 인스턴스의 참조 카운트를 증가시키지 않습니다.
    • 참조하는 인스턴스가 메모리에서 할당 해제될 경우 nil 이 할당됩니다.
  3. unowned reference

    • 참조하는 인스턴스의 참조 카운트를 증가시키지 않는다.
    • 참조하는 인스턴스가 메모리에서 할당 해제될 경우 별다른 조치를 취하지 않는다.

여기서 weakunowned는 참조 카운트를 증가시키지 않아 순환 참조를 해결할 수 있는 방법이 될 수 있습니다.
두 방식의 차이가 있다면, weak 와 다르게 unowned옵셔널 타입으로 선언하지 않아도 됩니다.
그 이유는 unowned 참조를 하면 참조하는 인스턴스가 메모리에서 할당 해제되어도 여전히 Heap 영역에 인스턴스가 있던 곳을 가리키고 있습니다. 즉, 참조하고 있던 주소만 가르키고 있는 허상 포인터가 됩니다.

그렇기 때문에, 참조하는 인스턴스가 먼저 메모리에서 해제될 가능성이 없는 경우에만 사용해야합니다.

weakunowned를 사용하여 순환 참조를 방지할 때 주의 사항

약한 참조의 예

class Person {
    var pet: Pet?
}

class Pet {
    weak var owner: Person?
}

'Pet'은 'Person'에 대한 약한 참조를 갖습니다.
'Person' 인스턴스가 더 이상 참조되지 않고 ARC에 의해 할당 취소되면 해당 'Pet'인스턴스의 owner는 자동으로 nil이 됩니다.
더 이상 가리키는 유효한 'Person' 인스턴스가 없기 때문입니다.

Define Capture List - 클로저 캡처 목록

Swift에서 캡처 목록은 클로저 외부의 값을 캡처하고 유지하는 방법에 대한 규칙을 정의하기 위해 클로저 내에서 사용됩니다.
Swift의 클로저는 정의된 컨텍스트에서 상수 또는 변수에 대한 참조를 캡처하고 저장할 수 있습니다. 이를 캡처 값이라고 합니다.
이는 강력한 기능이지만 특히 캡처된 변수가 클래스의 인스턴스인 경우 강력한 참조 순환으로 이어질 수 있습니다.

weak 예시

class MyClass {
    var property: String = "Hello"

    func doSomething() {
        let closure = { [weak self] in
            print(self?.property ?? "Default")
        }
        closure()
    }
}

이 예시에서 'self'는 클로저 내에서 약하게 캡처됩니다.
MyClass 인스턴스가 할당 해제되면 클로저 내부의 self는 nil이 되어 강력한 참조 순환을 방지합니다.

unowned 예시

class MyClass {
    var property: String = "Hello"

    func doSomething() {
        let closure = { [unowned self] in
            print(self.property)
        }
        closure()
    }
}

여기서 'self'는 소유되지 않은 상태로 캡처됩니다.
클로저가 생명주기동안 항상 'self'를 참조한다는 것을 알 때 사용됩니다.

클로저에서도 주의사항

참고

Swift-ARC(공식문서)

ha-nabi commented 11 months ago

ARC가 앱의 메모리 사용량을 자동으로 관리하는 데 사용되는 메모리 관리 기술이고, 클래스 인스턴스가 더 이상 필요하지 않을 때 할당이 취소되도록 한다라는거 이해했습니다.

강한 참조, 약한 참조 말은 들어보긴 했는데 자세하게 적어주니 참고하는데 도움이 많이 되었습니다 그럼에도 강한 참조, 약한 참조를 어떤경우에 사용해야하는지 모르겠습니다 굳이 저걸 안써도 잘 돌아가는데 써야 할 이유를 아직 못느끼는게 문제인거 같아요 그래서 저걸 써야만 하는 상황을 설명해 주시면 좋을거 같습니다 ㅎㅎ

hamfan524 commented 10 months ago

음 경고를 표시하는 뷰 컨트롤러로 예시를 들어보겠습니다. 경고하는 부분은 뷰 컨트롤러에 대한 참조가 필요하지 않는데 뷰컨트롤러를 참조하는 경우, 이 참조가 강력한 참조 순환이 되는 경우가 있을 수 있습니다.

약한 참조가 없는 예제 코드(문제 있는 경우)

class ViewController {
    var alert: Alert?

    func presentAlert() {
        alert = Alert(viewController: self)
        // 알림 보여주기
    }
}

class Alert {
    var viewController: ViewController

    init(viewController: ViewController) {
        self.viewController = viewController
    }

    func dismiss() {

    }
}

예시에서 ViewController에는 Alert에 대한 강력한 참조가 있고 Alert에도 ViewController에 대한 강력한 참조가 있습니다. 이 설정은 강력한 참조 순환으로 이어져 실제로 더 이상 사용을 하지 않는 상황에서 ARC가 자동 할당 해제를 해야하지만, 두 ​​개체 모두 할당이 해제되지 않을 수 있습니다.

약한 참조 사용 예제코드

class ViewController {
    var alert: Alert?

    func presentAlert() {
        alert = Alert(viewController: self)
        // 알림 보여주기
    }
}

class Alert {
    weak var viewController: ViewController?

    init(viewController: ViewController) {
        self.viewController = viewController
    }

    func dismiss() {

    }
}

이런 변경으로 인해 강력한 참조 순환이 방지됩니다.