[Swift] ARC 란 무엇인가

dsfasdfsfsfds·2022년 4월 17일
0

안녕하세요 제제로입니다😊
오늘은 iOS 개발자라면 한 번쯤은 들어봤을 법한 ARC라는 주제에 관해서 이야기해보려고 합니다.

ARC란??

https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html
위에 나와있는 내용을 번역해보면

Swift는 Automatic Reference Counting(자동 참조 계수)를 사용하여 메모리를 관리하고 추적합니다.
대부분의 경우 이는 메모리 관리가 Swift에서 "그냥 작동"된다는 것을 의미하며, 사용자가 직접 메모리 관리에 대해 생각할 필요가 없다는 것을 의미합니다.

흠.. 문서에 적혀 있는 내용을 번역해보니 Swift는 ARC를 사용해서 메모리를 관리하고 사용자가 설정하지 않아도 스스로 관리한다라고 나와있는 것 같습니다.
그러면 ARC는 어떻게 메모리를 관리할까요??

ARC의 메모리 관리 방법

ARC는 앱의 메모리를 관리하기 위해 해당 인스턴스가 더 이상 필요하지 않을 때 클래스 인스턴스에서 사용하는 메모리를 자동으로 해제합니다.

여기서 그러면 ARC는 인스턴스가 더 이상 필요하지 않은 지 어떻게 알까요??

ARC는 현재 각 클래스 인스턴스를 참조하는 속성, 상수 및 변수의 수(reference Count)를 추적합니다. ARC는 해당 인스턴스에 대한 활성 참조가 하나 이상 있는 한 인스턴스의 할당을 해제하지 않습니다. 이를 가능하게 하기 위해 속성, 상수 또는 변수에 클래스 인스턴스를 할당할 때마다 해당 속성, 상수 또는 변수는 인스턴스에 대한 강력한 참조 를 만듭니다. 참조는 해당 인스턴스를 확고하게 유지하고 해당 강력한 참조가 남아 있는 한 할당 해제를 허용하지 않기 때문에 "Strong" 참조라고 합니다.

다르게 말해서, 사용할 인스턴스는 강력한 참조를 만들어 ARC가 인스턴스의 할당을 해제하는 것을 막습니다.

Strong Reference Cycles Between Class Instances

ARC에서 발생할 수 있는 문제가 하나 있습니다.
앞서 말씀드렸다시피 사용할 인스턴스는 강력한 참조를 만들어 ARC가 인스턴스의 할당을 해제하는 것을 막는다고 말씀드렸는데
인스턴스를 사용하지 않지만 참조가 계속 유지되어서 해제가 안되는 상태가 발생하는 경우가 있습니다.
그림과 코드를 보면서 설명 드리겠습니다.

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")
unit4A = Apartment(unit: "4A")

현재 코드를 그림으로 나타내면 이렇습니다.
현재 john과 unit4A 변수에 각각 인스턴스가 할당돼서 강력한 참조가 만들어 졌습니다.

john!.apartment = unit4A
unit4A!.tenant = john

인스턴스 프로퍼티로 서로를 할당해줍니다.
프로퍼티에 인스턴스가 할당돼서 강력한 참조가 만들어 집니다.

john,unit4A의 인스턴스 할당 해제를 시켜봅시다.

john! = nil
unit4A! = nil
자 john과 unit4A와 클래스 인스턴스의 참조가 끊어졌기 때문에 deinit이 호출되고 할당이 해제 되어야 하지만 프로퍼티로 서로를 참조하고 있어서 해제가 되지 않습니다.

이 문제를 해결하기 위해 Weak을 사용하게 됩니다.
Weak은 참조를 하지만 약한 참조입니다.
Weak은 ARC가 할당 해제하는 것을 막지 못합니다.
이말은 즉슨 Weak은 reference count를 증가시키지 않습니다.

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 }
    weak var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}

이렇게 되면 Person instance에 대한 강한 참조는 john이라는 변수만 가지고 있기 때문에 john과 instance의 참조를 없애게 되면 정상적으로 instance가 할당 해제 될 것 입니다.

john! = nil

Weak

weak을 사용할 때에는 주의할 점이 있습니다.
weak으로 선언한 변수는 참조하는 동안 해당 인스턴스가 할당 해제 될 수 있습니다.
왜냐하면 reference Count를 증가시키지 않기 때문에 다른 곳에서 강한 참조가 없어지게 돼서 reference Count가 0이 되면 ARC가 인스턴스를 할당 해제 시키기 때문입니다.
할당 해제 될 때 ARC는 weak 으로 선언된 변수에다가 자동으로 nil을 할당합니다. 따라서 Optional 변수로 선언해야합니다.

Unowned

weak처럼 unowned도 참조하는 인스턴스를 강하게 유지하지 않습니다.
weak와 차이점은 unowned로 참조하는 인스턴스는 항상 값이 존재해야 합니다.
즉 ARC가 참조하는 인스턴스를 할당 해제 해도 nil을 할당하지 않습니다.
인스턴스가 할당 해제 되고 해당 인스턴스에 접근하려고 하면 에러가 난다.
따라서 unowned로 참조하는 인스턴스가 먼저 메모리에서 해제될 일이 없을 때 사용됩니다.

+) unowned은 nil을 할당받지 않아서 Non-Optional Type으로 선언하고 let도 가능했지만
Swift 5.0부터 Optional Type도 가능하다고 한다.
흠 ... nil을 할당받지 않는데 왜 Optional Tyle도 가능하게 한건지는 궁금하다 이건 다시 알아봐야겠다

Strong Reference Cycles for Closures

클래스 인스턴스의 속성에 Closure를 할당하고 해당 Closure에서 인스턴스를 캡처하는 경우에도 강력한 참조 주기가 발생할 수 있습니다.

왜 발생하냐 ??

클로저는 참조 타입입니다. 따라서 class의 프로퍼티로 클로저를 선언하면 해당 클로저에 대한 강한 참조가 생기게 됩니다. 그리고 클로저에서 인스턴스를 접근하게 되면 해당 인스턴스를 캡쳐하게 됩니다. 이때 캡쳐를 Strong Reference로 하기 때문에 클로저와 인스턴스 간의 강한 순환 참조가 발생하게 됩니다.

class HTMLElement {

    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) is being deinitialized")
    }

}
이 때 강한 순환 참조를 해결하기 위해 캡쳐리스트에 명시적으로 weak,unowned로 인스턴스를 캡쳐하면 됩니다!

    lazy var asHTML: () -> String = {
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }

마무리

다음 글에선 ARC에 대해서 조금 더 깊이 다루도록 하겠습니다

감사합니다😊
건강한 지적은 언제나 환영합니다

profile
fdsafsdafsda

0개의 댓글