ARC - Automatic Reference Counting

Eli·2021년 2월 7일
2

Swift

목록 보기
16/17
post-thumbnail

How ARC Works

Swift에서는 대부분의 경우에는 인스턴스의 할당과 해제를 자동적으로 처리하게 되어 개발자가 신경 쓸 필요가 없다고 문서에 쓰여져 있다.

신경을 쓰지 않아도 되는 경우는 아래와 같다.
새 인스턴스가 생성 될 때, ARC는 인스턴스를 담는 메모리를 할당한다.
그 후 계속해서 인스턴스가 참조되는 곳을 추적하며 참조의 수를 카운팅.
기본적으로 인스턴스가 사용되지 않으면(참조가 0이 되면) 자동적으로 메모리에서 해제를 진행.

몇몇의 경우에서는 사용하지 않지만 (0이 되지 않아) 메모리가 누수되는 경우가 있어 이를 해결하는 부분을 이곳에서 다룬다.

클로저를 사용할 때 쉽게 발생할 수 있는데, 더 좋은 코드를 위해 꼭 필요한 부분이지 싶다.

ARC in Action

ARC가 작동하는 과정.

class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
}

var reference1: Person?
var reference2: Person?
var reference3: Person?

//reference count 증가 (총 1회)
reference1 = Person(name: "John Appleseed")
//reference count 증가 (총 2회)
reference2 = reference1
//reference count 증가 (총 3회)
reference3 = reference1

// 1 감소 (총 2회)
reference1 = nil
// 1 감소 (총 1회)
reference2 = nil
// 1 감소 (총 0회) 해당 시점에 메모리에서 해제
reference3 = nil

Strong Reference Cycles Between Class Instances

서로를 참조하여 더 이상 사용되지 않지만 메모리에서 해제가 되지 않아, 메모리 누수가 발생하는 경우로 아래와 같은 예시로 이루어진다.

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    var tenant: Person?
}

var john: Person? = Person(name: "Jone")
var unit4A: Apartment? = Apartment(unit: "4A")

john?.apartment = unit4A
unit4A?.tenant = john
//위의 상황에서 각각 2씩 참조가 형성

//아래 코드로 참조를 1씩으로 줄이지만
//서로를 참조하고 있어 여전히 reference count 1인 상황
john = nil
unit4A = nil

//위와 같은 상황에서 더 이상 사용되지 않지만 강한순환참조에 빠져 메모리 누수가 발생하게 됨.

Resolving Strong Reference

위의 문제를 해결하기 위해 강한 참조가 아닌 다른 참조방식을 제공.

아래의 방식 모두 참조 회수를 증가시키지 않고 인스턴스를 참조.

  1. weak

    메모리에서 해제되면 자동으로 nil 할당

  2. unowned

    unwrapping optional로 메모리가 해제 될 일이 없다는 가정하에 사용.

//위와 같은 상황에서 tenant를 weak 또는 unowned로 선언
class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    weak var tenant: Person?
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john?.apartment = unit4A
unit4A?.tenant = john
//아래와 같은 관계 형성

john = nil
// ARC에서 아래 그림과 같이 Person 인스턴스 메모리를 해지
unit4A = nil
//Apartment 인스턴스도 해제가 되어 메모리 누수가 없는 상태로
//효율적인 리소스 활용이 가능

Strong Reference Cycles for Closures

위의 문제 뿐만 아니라 클로저 관계에 대해서도 강한순환 참조에 빠질 수 있으며,

이를 해결하기 위해 캡쳐 리스트를 사용해 해결한다.

비동기 처리 등에서 효율적으로 활용할 수 있다.

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
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

//위와 같은 경우에 아래의 사진과 같이 클로저 내부의 캡처 참조로
//강한 참조에 빠지게 된다.

해결

캡쳐하는 대상에 대한 참조타입을 지정해, 강한 참조에 빠질 수 있는 경우를 대비

//사용방법 코드
lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // closure body goes here
}

//위의 문제의 해걸
class HTMLElement {
    let name: String
    let text: String?

		//캡쳐하는 self를 weak으로 참조해 카운팅을 증가시키지 않음
    lazy var asHTML: () -> String = { [weak self] in
				//self가 해제되어 nil이 될 수 있으므로 그에대한 처리
				guard let self = self else { return }

        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
    }
}

결국 캡처의 참조 방법을 지정하면서 아래와 같은 참조 구조를 가지면서 강한 순환 참조를 해결 할 수 있다.

#학습에 대한 내용으로 틀린 내용이 있을 수 있습니다.
#댓글로 남겨주시면 더 좋은 게시글로 수정하도록 하겠습니다.

profile
애플을 좋아한다. 그래서 iOS 개발을 한다. @Kurly

0개의 댓글