본문 바로가기

난 iOS개발자/iOS

ARC(Auto Reference Counting)

728x90

Swift는 Auto Reference Counting 으로 앱의 메모리를 관리한다.

ARC는 참조유형인 클래스나 클로저에 적용되며 구조체나 열거형은 참조유형이 아니므로 추적하지 않는다.
클래스 인스턴스는 heap메모리 공간에 동적으로 할당한다. 이것을 관리하기 위해서는 heap영역에 참조형 자료들이 얼마나 참조되고 있는지 카운팅 하고 이에 따라 메모리를 할당 및 제거하면 된다. ARC는 인스턴스가 더 이상 필요하지 않을 때 클래스 인스턴스에서 사용하는 메모리를 자동으로 해제한다.

 

 

간단한 코드를 보며 ARC의 RefCount를 어떻게 관리하는지 이해해보자.

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

Person클래스에는 인스터스 속성을 초기화하고 메시지를 print하는 이니셜라이저가 있다.
그리고 인스턴스가 할당 해제될때 메시지를 인쇄하는 초기화 해제 프로그램도 있다.

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

또 Person의 인스턴스를 생성하기 위한 변수 세 개를 정의했다.

세 변수 중 하나의 변수에만 Person인스턴스를 할당해보자.

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

 

다른 두 변수에는 refernce1을 할당한다.

reference2 = reference1
reference3 = reference1

이 경우 reference2, reference3 모두 reference1이 참조하고 있는 Person인스턴스르 참조하게 된다.
세개의 변수가  Person인스턴스에 대한 참조를 가지므로 Person인스턴스의 refCount는 3이 된다.

reference1 = nil
reference2 = nil

두개의 참조를 nil했지만 reference3과의 참조가 남아 있기 때문에 Person인스턴스가 할당 해제되지 않는다.

ARC는 Person의 세번째 인스턴스 reference3의 참조가 끊어질 때까지 인스턴스 할당 해제 하지 않는다.

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

deinit메시지가 출력된다.

 

 

클래스 인스턴스 간 강한 참조

 

인스턴스간 강한 참조로 인해 참조가 0이 되지않는 코드를 작성할 수도 있다.
예를 들어 두 클래스 인스턴스가 서로의 클래스타입을 멤버속성으로 지니고 있는 상태에서 각 멤버속성을 인스턴스로 초기화하는 경우이다. 

john과 unit4A가 nil을 할당받아도 deinit이 불려지지 않는다. 이러한 강한참조는 메모리 누수를 발생시킨다.

 

 

weak을 사용한 강한참조 해결

약한 참조는 강한참조를 유지하지 않으므로 강한참조의 일부가 되는것을 막는다.
속성앞에 weak키워드를 배치하여 약한 참조를 나타낼 수 있다.

weak var tenant: Person?

약한 참조가 참조하는 동안 해당 인스턴스가 할당 해제될 수 있다.
따라서 ARC는 참조하는 인스턴스가 할당 해제될 때 약한 참조를 nil로 자동 설정한다.

 

 

문제 피하기

 

강한참조를 피하기 위한 방법을 생각해보자.

class Traveler {
	var name: String
	var account: Account?
}

class Account {
	var traveler: Traveler?
	var points: Int
	func printSummary() {
		if let traveler = traveler {
			print("\(traveler.name) has \(points) points")
		}
	}
}

let traveler = Traveler(name: "Lily")
let account = Account(traveler: traveler, points: 1000)
traveler.account = account
account.printSummary()

위 코드를 보자. 생각해보면 Account클래스는 Traveler클래스를 직접적으로 참조할 필요가 없다. 단지, Traveler의 이름만 필요한것이다.

이럴땐 PersonalInfo라는 타입을 새롭게 생성하여 여기에 name을 담는다면 순환참조하는 구조에서 벗어날 수 있다.
잠재적인 버그 가능성을 줄이고 확장성 측면에서 더욱 괜찮은 코드가 된다.

 

 

 

내부 동작 들여다보기

-ARC는 컴파일러의 주도로 컴파일 타임에 소스코드를 검사하고 할당/해제가 필요한 부분에 retain과 release코드를 삽입한다.

retain은 런타임중 reference count를 증가시키고, release는 감소시킨다. Reference count가 0이 되면 인스턴스를 할당 해제한다.

 

위 코드를 보면 traveler2 를 위해서 retain 이 삽입되고 이를 통해서 Reference Count는 2로 증가한다.
특이한 점은 retain 의 경우는 traveler2 가 명시되기 이전에 삽입되고 release 는 사용이 끝난 다음에 삽입된다는 이다.

 

 

-ARC의 메커니즘은 swift runtime이라는 library에 구현되어 있다.
Swift runtiem은  동적 할당되는 모든 object를 heapObject라는 struct로 표현한다. heapObject에서는 swift에서 객체를 구성하는 모든 데이터, 즉 reference count와 type meta data를 포함하고 있다.

 

- 다른 언어들은 괄호가 닫힐 때 까지 생명주기가 보장된다고 한다. 그러나 Swift의 경우 항상 사용이 끝나는 시점에서 해제되는 것은 아니며, 정확하게 그 시점을 알아내기는 애매하다. ARC의 최적화에 따라 그 정도가 정해진다고 한다.

 

728x90

'난 iOS개발자 > iOS' 카테고리의 다른 글

Global DispatchQueue의 QoSClass  (0) 2022.05.10
NSCache  (0) 2022.05.07
String을 Subscript로 접근할 수 없는 이유  (0) 2022.05.04
Class의 성능을 향상 시키는 방법  (0) 2022.05.03
initialization -3  (0) 2022.04.29