개발하는 동글 :]

[TIL],[Swift],[Reference Cycle],[Retain Cycle] 본문

카테고리 없음

[TIL],[Swift],[Reference Cycle],[Retain Cycle]

동글하다 2023. 8. 11. 18:57

Why?

api사용을 연습해 보던 중 튜터님이 retain cycle이 생길 것 같다는 조언을 해주셔서 retain cycle이 뭔지 공부시작

TIL

1. Swift는 어떻게 메모리를 관리할까?

 

Swift의 메모리는 대부분 ARC(automatic reference counting)가 관리해 준다.

기본적으로 클래스의 객체를 가리키는 각각의 reference(참조)는 강한 참조이다.

강한 참조가 있는 한 이 객체의 메모리는 해제되지 않을 것이다.

만일 객체에 대한 강한 참조가 존재하지 않는다면 이는 메모리에서 해제될 것이다.

ARC
- Swift의 메모리 사용량 추적 및 관리 시스템. 
- ARC는 더 이상 필요하지 않은 클래스 인스턴스를 자동으로 메모리에서 해제한다. 

 

import UIKit

class Test {
    init(){
        print("init")
    }
    deinit{
        print("deinit")
    }
}

var testClass: Test? = Test() // init
testClass = nil // deinit

init과 deinit 을통하여 메모리에 올라가고 해제되는 것을 확인할 수 있다.

init: 클래스의 인스턴스가 메모리에서 등록되는 시점에 호출
deinit: 클래스의 인스턴스가 메모리에서 해제되는 시점에 호출

 

2. Reference Cycle? Retain cycle?

Reference Cycle == Retain cycle

 

https://developer.apple.com/library/archive/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html

 

Transitioning to ARC Release Notes

Transitioning to ARC Release Notes Important: This document is no longer being updated. For the latest information about Apple SDKs, visit the documentation website. Automatic Reference Counting (ARC) is a compiler feature that provides automatic memory m

developer.apple.com

문서 내용 중

reference cycles(previously known as retain cycles~)

이라는 내용이 있기에 Reference Cycle이 과거 Retain cycle이라 불렸던 것이기에 같은 뜻이라 볼 수 있다.

 

3. Reference Cycle?

두 클래스 인스턴스가 서로에 대한 강력한 참조를 유지하여 각 인스턴스가 서로를 활성 상태로 유지하는 경우
class TestClass{
    var testClass: TestClass? = nil
    init(){
        print("init")
    }
    deinit{
        print("deinit")
    }
}

var testClass1: TestClass? = TestClass() //init
var testClass2: TestClass? = TestClass() //init

testClass1?.testClass = testClass2
testClass2?.testClass = testClass1

testClass1 = nil
testClass2 = nil

다음의 예시는 init은 출력이 되지만 deinit은 출력이 되지 않은 것을 확인할 수 있다.

즉 testClass1과 testClass2는 메모리에서 해제되지 않았다는 것이다.

왜 이런 일이 일어날까?

testClass1?.testClass = testClass2
testClass2?.testClass = testClass1

이 코드를 통해 서로를 참조하고 있는 형태가 된다.

그 후  아래의 코드를 통해 nil을 할당해 보면

testClass1 = nil
testClass2 = nil

각각의 객체는 강한 참조가 하나씩 줄어들지만 각각의 객체는 아직 내부적으로 한 개씩의 참조를 가지고 있다. 이는 두 객체들의 메모리가 해제되지 않을 것이라는 걸 의미한다. 심지어 더 심각한 것은 이 두 객체에 대한 참조는 우리의 코드에서 더 이상 존재하지 않는다 즉 이 두 객체의 메모리를 해제하는 방법은 존재하지 않는다. 그리고 이러한 현상을 강한 참조 순환이라고 하며 이러한 메모리 누수가 몇 군데 발생하게 된다면 사용할 때마다 메모리의 사용량이 증가하게 된다. 그리고 이러한 메모리 사용량이 높다면 iOS는 애플리케이션을 종료하게 된다.

 

4. Reference Cycle 해결

1. Weak

소위 말하는 “약한 관계”를 사용함으로써 Retain Cycle을 피할 수 있다. 참조를 weak으로 선언한다면 이것은 “강한 참조”가 되지 않는다. 

class TestClass{
    weak var testClass: TestClass? = nil  // 이제 이 참조는 약한 참조이다!
    init(){
        print("init")
    }
    deinit{
        print("deinit")
    }
}

var testClass1: TestClass? = TestClass() //init
var testClass2: TestClass? = TestClass() //init

testClass1?.testClass = testClass2
testClass2?.testClass = testClass1

testClass1 = nil //deinit
testClass2 = nil //deinit

weak 사용 후의 형태

약한 관계만이 남아있다면 객체들의 메모리는 해제된다.

즉 weak reference는 참조는 할 수 있지만 Reference Count가 증가되지 않는다

 

객체의 메모리가 해제된 후 그에 대응하는 변수는 자동으로 nil이 된다.

만약 변수가 이미 메모리가 해지된 객체의 영역을 가리키고 있다면 프로그램은 runtime exception을 발생시킨다

또한 optional 타입만이 nil값이 될 수 있기 때문에 모든 weak 참조 변수는 반드시 optional 타입이어야 한다.

 

2. Unowned

weak 밖에도 변수에 적용할 수 있는 unowned라는 옵션이 존재한다. weak과 매우 비슷한 역할을 하지만 한 가지 예외가 있다.

unowned로 선언된 변수는 nil로 될 수 없다. 그러므로 unowned 변수는 optional로 선언되어서는 절대 안 된다.

하지만 메모리가 해제된 영역의 접근을 시도한다면 애플리케이션은 runtime exception을 발생시킨다.

이 뜻은 unowned는 해제된 메모리 영역을 접근하지 않는다는 확실한 경우만 사용해야 한다는 뜻이다.

 

 

 

Reference

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting/

https://zeddios.tistory.com/1213

https://baked-corn.tistory.com/30