아직은 개린이

[Swift] ARC 본문

Swift + iOS/Swift

[Swift] ARC

jiyeonlab 2020. 2. 26. 21:04

ARC(Automatic Reference Counting)

iOS는 앱의 메모리 사용을 추적, 관리하기 위해 ARC 기능을 사용한다.

ARC는 Automatic Reference Counting의 줄임말로 참조 메모리 관리를 자동으로 해주는 기능을 뜻한다. 

인스턴스가 참조되거나 참조해제될 때 횟수를 카운팅하고, 횟수가 0이 되면 인스턴스를 메모리에서 해제하는 방식으로 수행된다.

 


How ARC Works

클래스가 새로운 인스턴스를 생성할 때, ARC는 인스턴스에 대한 정보를 저장할 메모리 청크를 할당한다.

할당한 메모리 청크에는 인스턴스의 타입에 대한 정보, 인스턴스와 관련된 프로퍼티 값 등을 저장해둔다. 

더이상 인스턴스가 필요하지 않게 되면 자동으로 메모리에서 비우는 방식으로 메모리를 관리한다.

 

하지만 ARC가 사용중인데 인스턴스를 해제하면 인스턴스의 프로퍼티나 메소드에 접근할 수 없는데이 때 접근하려 하면 앱에서 crash가 난다!

따라서 인스턴스를 필요로 하는 동안에는 메모리에서 사라지지 않게 하기 위해, 인스턴스 할당 시 strong reference를 연결하고 ARC가 인스턴스의 참조 횟수를 추적한다.

 

ARC in Action

예시를 통해 살펴보자!

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

 

위 코드는 name이라는 상수 프로퍼티를 가지고 있는 Person  클래스를 선언한 것이다.

Person 클래스는 인스턴스의 name 프로퍼티를 set하고, 초기화됐음을 알리는 메시지를 출력하는 initializer와 클래스 인스턴스가 메모리에서 해제될 때 메시지를 출력하는 deinitializer가 있다.

 

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

reference1 = Person(name: "John Appleseed")
// Prints "John Appleseed is being initialized"

위 코드에서는 Person 클래스 인스턴스를 설정하기 위한 Person? 타입의 변수를 3개 선언한 후, reference1에 새로운 Person 클래스 인스턴스를 할당했다.

이때 reference1 변수와 Person 클래스의 인스턴스가 강한 참조로 연결된다.

강한 참조로 연결되어 있기 때문에 ARC는 Person 클래스가 메모리에서 할당 해제되지 않고 유지되도록 한다.

 

reference2 = reference1
reference3 = reference1

나머지 두 개의 변수에 위에서 만든 Person 인스턴스를 할당했다.

이렇게 되면 하나의 Person 클래스 인스턴스에 총 3개의 강한 참조가 연결된다.

 

reference1 = nil
reference2 = nil

만약 강한 참조 중 2개에 nil을 할당하게 되면, Person 클래스 인스턴스에는 하나의 강한 참조만 남게 된다.

 

이 때까지도 Person 인스턴스가 메모리에서 해제된 것은 아니다!

아직 하나의 강한 참조가 남아있기 때문이다.

reference3 = nil
// Prints "John Appleseed is being deinitialized"

 이렇게 하나 남은 강한 참조까지 끊어줘야 Person 클래스 인스턴스가 메모리에서 할당 해제된다.

 

Strong Reference Cycles Between Class Instances

위의 예시를 통해 ARC는 새로운 인스턴스가 생성될 때 참조 수를 추적하고, 더 이상 필요하지 않을 때 해당 인스턴스를 할당 해제한다는 것을 확인 할 수 있었다.

 

하지만 ARC의 문제는 두 개의 클래스 인스턴스가 서로를 참조하는 경우에 strong reference cycle이 발생할 수 있다는 것이다.

서로를 강한 참조로 참조하게 되면, 레퍼런스 카운트가 0이 될 수 없게 되고 영원히 메모리에서 해제되지 않게 된다.

메모리 누수가 발생하게 된다!

 

어떤 상황에서 strong reference cycle이 발생하는지 살펴보자.

 

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

Person 클래스와 Apartment 클래스는 각각 String 타입의 상수를 하나씩 가지고 있고, Person 클래스는 Apartment? 타입의 변수 하나, Apartment 클래스는 Person? 타입의 변수 하나를 가지고 있다. 또한. 각각 클래스는 deinit 함수를 갖고 있다.

 

var john: Person?
var unit4A: Apartment?

각각 john, unit4A 변수는 Person과 Apartment의 인스턴스로 선언되는데, 옵셔널 타입이기 때문에 초기값은 nil이다.

 

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

john, unit4A 변수에 각각 Person 인스턴스와 Apartment 인스턴스를 할당해준다.

 

 

이렇게 되면 아래의 도식처럼 john 변수는 Person 인스턴스에 강한 참조를 갖고, unit4A 변수는 Apartment 인스턴스에 강한 참조를 갖게 된다.

 

각 클래스 인스턴스에 강한 참조를 갖는 변수들

 

이 때, john의 apartment 프로퍼티에 Apartment의 인스턴스인 unit4A를 넣어주고, unit4A의 tenant 프로퍼티에 Person의 인스턴스인 John을 넣어주게 되면, 아래와 같은 참조 관계를 갖게 된다.

 

strong reference cycle 발생

 

이제 Person 인스턴스는 Apartment 인스턴스에 강한 참조를 갖게 되고, Apartment 인스턴스는 Person 인스턴스에 강한 참조를 갖게 됨으로써 john과 unit4A는 strong reference cycle로 연결된다.

 

reference count가 영원히 0이 될 수 없음

이제는 john = nil, unit4A = nil을 넣어서 각각 참조를 끊어도 참조 계수가 0이 되지 않고, ARC에서 인스턴스를 할당 해제하지 않는다!

 

이러한 strong reference cycle을 해결하기 위한 방법으로는 weak, unowned 참조가 있다. 

 

이 두가지 방법에 대해서는 다음 글 에서 이어서 정리해보자!

 


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

'Swift + iOS > Swift' 카테고리의 다른 글

[Swift] weak, unowned  (0) 2020.02.27
[Swift] CustomStringConvertible  (0) 2020.02.10
[Swift] lazy  (0) 2020.02.10