티스토리 뷰

Swift

[Swift] ARC (Automatic Reference Counting) 정리

Hani_Levenshtein 2021. 5. 17. 09:49

안녕하세요 Hani입니다.☺️

이번에는 자동 참조 카운트(ARC)에 대하여 알아볼게요.


 

스위프트는 앱의 메모리 사용을 추적하고 관리하기 위해 ARC라는 놈을 사용합니다.

ARC는 참조 타입의 객체에 대하여 자동적으로 메모리를 관리해주는 장치입니다.

ARC가 없었던 시절에는 개발자들이 수동적으로 메모리를 관리해주는 코드를 넣어서 메모리를 할당/해제해야 했답니다. 🥺

 

 

그럼 ARC는 어떻게 일하길래 자동적으로 메모리를 관리해주느냐..!

 

 

먼저, ARC는 클래스의 인스턴스를 생성할 때마다 그에 맞게 메모리를 할당하여 인스턴스를 메모리에 적재시킵니다.

class Person {
        let name: String
        init(name: String) {
            self.name = name
            print("\(name) is being initialized")
        }
        deinit {
            print("\(name) is being deinitialized")
        }
    }
    
var reference1: Person?
var reference2: Person?
var reference3: Person?
//아직 인스턴스는 생성되지 않았음.

reference1 = Person(name: "Hani Levenshtein")
//여기서 인스턴스가 생성돼서 메모리에 적재된 후
//Hani Levenshtein is being initialized 가 출력됨!

 

Person(name: "Hani Levenshtein")이라는 객체는 메모리의 힙 영역에 할당되고,

reference1이라는 변수가 메모리의 스택 영역에서 Person클래스의 인스턴스의 주소를 참조하고 있는 모습입니다.

Ref 1이 주소를 참조하는 모습

 

 

그런데 인스턴스를 참조하고 있는 변수가 있는데 인스턴스를 메모리에서 해제되면 안되겠죠?

Ref 1은 헛것을 참조하고 있다.  

위와 같은 모습은 앱의 Crush를 유발합니다.

 

 

이를 막기 위해 ARC는 객체가 얼마나 참조되고 있는지 카운트하는 변수를 우리 몰래 만들어서 사용 중이랍니다.

class Person {
    //var refCount: Int = 0
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}

var reference1: Person?
var reference2: Person?
var reference3: Person?
    
reference1 = Person(name: "Hani Levenshtein")
//refCount = 1
reference2 = reference1
//refCount = 2
reference3 = reference1
//refCount = 3

reference1 = nil
//refCount = 2
reference2 = nil
//refCount = 1
reference3 = nil
//refCount = 0
//모든 참조가 사라진 후에야 객체가 메모리에서 사라짐..!
//Hani Levenshtein is being deinitialized

 

클래스의 인스턴스가 생성된 직후

 

클래스의 인스턴스에 대한 참조횟수가 늘어남

 

 

 

인스턴스에 대한 참조횟수가 0이되면 메모리에서 해제됨

 

위의 예시처럼 ARC는 클래스의 인스턴스의 참조 횟수가 0이 되기 전까지 메모리를 해제하지 않아요.

 

 

사실 참조 횟수를 증가/감소시켜주는 retain/release 코드를 ARC가 컴파일 타임에 적절한 위치에 삽입시킨 것입니다.

reference1 = Person(name: "Hani Levenshtein")
//retain(reference1)
reference2 = reference1
//retain(reference2)
reference3 = reference1
//retain(reference3)

reference1 = nil
//release(reference1)
reference2 = nil
//release(reference2)
reference3 = nil
//release(reference3)

 

ARC 덕분에 작성해야할 코드의 양이 줄어들었다.

읭 컴파일 타임?

 

 

 

 

소스코드를 기계어로 변환시켜 실행 가능한 프로그램이 되도록 만드는 과정을 컴파일 타임이라고 하는데요..!

이때 코드에 잘못된 문법이 있으면 컴파일을 중지하고 소스코드의 잘못된 라인을 알려줍니다.

 

컴파일 타임이 마무리되어 실행 가능한 프로그램이 되면 사용자가 직접 프로그램을 동작시킬 수 있어요.

사용자가 프로그램을 동작시키는 모든 시간을 런타임이라고 합니다.

 

 

하여튼.. 컴파일 타임에 retain/release코드가 소스코드 중간중간에 들어가게 되고,

삽입된 코드를 포함한 모든 소스코드는 런타임에 동작하게 됩니다.

 

 

런타임 동안에 사용하지 않는 인스턴스들은 바로바로 메모리에서 해제하여 제한된 메모리를 효율적으로 사용해야겠죠?

 

ARC는 참조 타입 객체에 대한 모든 참조를 추적하여 객체의 생명 주기를 관리합니다. 

참조 횟수가 0이 된 객체를 메모리에서 할당 해제시키는 것으로 말이죠.

 

 

그런데 인스턴스의 참조 횟수가 0으로 내려가지 않는 경우가 있습니다.

 

코드를 보면서 이해해봅시당

 

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) is being deinitialized") }
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
//Person(name: "John Appleseed")'s refCount = 1

unit4A = Apartment(unit: "4A")
//Apartment(unit: "4A")'s refCount = 1

Person클래스의 인스턴스와 Apartment클래스의 인스턴스가 각각 하나씩 생성된 모습입니다.

그리고 john이라는 변수와 unit4A라는 변수가 각 인스턴스를 참조하고 있습니다.

각 인스턴스의 참조 횟수는 1입니다.

 

 

john!.apartment = unit4A
//Apartment(unit: "4A")'s refCount = 2

unit4A!.tenant = john
//Person(name: "John Appleseed")'s refCount = 2

각 변수가 참조하는 인스턴스의 프로퍼티가 상대방의 인스턴스를 서로 참조하여 참조 횟수가 2로 늘어난 모습입니다.

 

 

john = nil
//Person(name: "John Appleseed")'s refCount = 1

unit4A = nil
//Apartment(unit: "4A")'s refCount = 1

 

이제 더 이상 변수가 인스턴스를 참조하지 않습니다.

때문에 각 인스턴스는 필요가 없으니 메모리에서 해제되어야 하는데..

 

변수가 참조하던 인스턴스의 프로퍼티는 여전히 상대 인스턴스를 참조하고 있기 때문에 메모리에 여전히 남아있습니다. 🥺

이를 Strong Reference Cycle 강한 순환 참조라고 하는데, 

조치를 취해주지 않으면 두 인스턴스는 메모리에서 사라지지 않을 거예요. 🥺🥺🥺

 

 

Strong Reference Cycle에 대해선 다음 포스팅에서 알아보겠습니다.


References

https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html

http://blog.benjamin-encz.de/post/compile-time-vs-runtime-type-checking-swift/

https://medium.com/ivymobility-developers/memory-management-in-swift-by-arc-62bf4f0e2ff5

'Swift' 카테고리의 다른 글

[Swift] 클로저 강한 순환 참조  (0) 2021.05.19
[Swift] 클래스 강한 순환 참조  (0) 2021.05.17
[Swift] 일급 객체 First class Citizen  (0) 2021.05.15
[스위프트] 클로저 정리  (0) 2021.05.14
[Swift] Init vs Convenience Init  (0) 2021.05.01
댓글