[iOS] ARC

유인호·2024년 2월 21일
0

iOS

목록 보기
30/64

1. ARC란 무엇인지 설명하시오.

Automatic Reference Counting

이름에도 유추할수 있듯 레퍼런스(참조)를 자동으로 처리해주는 기술.

참조타입 (클래스 등)의 실질적인 데이터는 힙 영역에 저장되고, 인스턴스를 생성할 경우 힙 영역에 있는 데이터가 저장된 주소를 스택 영역에 기록하게 된다. 이를 쉽게 관리하기 위해 ARC라는 기술이 도입이 되었다.

objc시절에는 MRC(Manual Reference Counting)라고, 수동으로 관리하였다.

MRC -> ARC로 넘어오게 되었는데, MRC 코드까지 볼 필요는 없을 것 같고, retain과, release 두가지를 짚고 넘어가자면,

  • retain -> Retain Count를 1 증가 시킨다.
  • release -> Retain Count를 1 감소 시킨다.

컴파일 시점에 컴파일러가 retain과 release의 코드를 적절하게 알아서 삽입을 해주어서, 런타임 때 정상적으로 retain과 release가 작동하는것.


2. Retain Count 방식에 대해 설명하시오.

참조타입의 실질적인 데이터는 힙 영역에 있다고 했었는데, 그럼 이 데이터들을 어떻게 개발자가 관리를 하느냐? Retain Count를 통해서 관리를 해줄 수 있다.

힙 영역을 가르켜서(인스턴스를 만들었을 때) 스택 영역에 쌓이게 되면, Retain Count가 1 증가하여 힙 영역에 기록된다. 이것이 retain이고, 스택에서 pop되거나, nil로 할당하는 등 사용하지 않게 되면 Retain Count가 1 감소하여 힙 영역에 기록된다.

이때, 총 Retain Count가 0이 되면 Class의 소멸자(deinit)이 실행되어 소멸되게 된다.


3. Strong 과 Weak 참조 방식에 대해 설명하시오.

  • strong - 데이터를 사용하며 Retain Count를 1 늘리겠다.
  • weak, unowned - 데이터를 사용하지만 약하게 또는 소유하지 않음으로써 Retain Count를 늘리지 않겠다.

swift에서 기본적으로 참조타입의 인스턴스를 생성하면 strong으로 참조하게 된다.

Class A, Class B를 각각 만들어, 서로를 참조한다는 가정을 해보자.

class ClassA {
	var b: ClassB?

	init() { print("ClassA init") }
	deinit { print("ClassA deinit") }
}

class ClassB {
	var a: ClassA?
	
	init() { print("ClassB init") }
	deinit { print("ClassB deinit") }
}

이 코드를 기반으로 아래 내용들을 정리해보자면,

  1. weak(약한 참조)
    약한 참조를 하게 되면, 앞서 언급했듯 Retain Count가 늘어나지 않는다.
class ClassA {
	var b: ClassB?

	init() { print("ClassA init") }
	deinit { print("ClassA deinit") }
}

class ClassB {
	weak var a: ClassA?

	init() { print("ClassB init") }
	deinit { print("ClassB deinit") }
}

var classA: ClassA? = ClassA() // ClassA Count + 1 (Strong)
var classB: ClassB? = ClassB() // ClassB Count + 1 (Strong)

classA.b = classB // ClassB Count + 1 (Strong)
classB.a = classA // ClassA Count Not add (Weak)

classA = nil // A는 count가 1이였고, nil을 할당함으로써 0이 되어 deinit.
print(classB.a) // nil (weak로 설정했기에 자동으로 nil이 되었다.)

weak는 print(classB.a)의 사례처럼 본인의 의도와 관계 없이 nil이 될 수 있기에 무조건 옵셔널 타입으로 선언해야 한다.

  1. unowned(미소유 참조)
    미소유 참조도 Retain Count가 늘어나지 않는다. 또한 참조하는 인스턴스가 메모리에 무조건 존재할 것이라는 전제를 기반으로 한다.
class ClassA {
    var b: ClassB?

    init() { print("ClassA init") }
    deinit { print("ClassA deinit") }
}

class ClassB {
    unowned var owner: ClassA  

    init(owner: ClassA) {
        self.owner = owner
        print("ClassB init")
    }
    deinit { print("ClassB deinit") }
}

var classA: ClassA? = ClassA() // ClassA Count + 1 (Strong)
if let classA {
   var classB: ClassB? = ClassB(owner: classA) // ClassB Count + 1 (unowned), ClassA Count + 1 (strong)
}
classA?.b = classB // ClassB Count + 1 (strong)
classA = nil // ClassA Count - 1 
classB = nil // ClassB Count - 1 

if let애서 classA가 있는지 없는지 확인하고 넣었다. classB의 owner는 classA의 데이터가 실제로 있지 않으면 사용할 수 없기 때문.


4.순환 참조에 대하여 설명하시오.

2개 이상의 클래스가 서로가 서로를 참조할때, Retain Count가 0이 되지 않아 메모리에서 해제 되지 않는 상황을 의미한다.


5.강한 순환 참조 (Strong Reference Cycle) 는 어떤 경우에 발생하는지 설명하시오.

순서가 바뀐거같은데, Strong하게 서로를 참조하게 되면, 불사의 상태가 되어버린다.

class ClassA {
	var b: ClassB?

	init() { print("ClassA init") }
	deinit { print("ClassA deinit") }
}

class ClassB {
	var a: ClassA?

	init() { print("ClassB init") }
	deinit { print("ClassB deinit") }
}

var classA: ClassA? = ClassA() // ClassA Count + 1 (Strong)
var classB: ClassB? = ClassB() // ClassB Count + 1 (Strong)
classA.b = classB  // ClassB Count + 1 (Strong)
classB.a = classA // ClassA Count + 1 (Strong)

classA = nil // ClassA Count - 1
classB = nil // ClassB Count - 1

이렇게 되버리면, Retaion Count 1씩 남아버렸고, 더이상 프로그래머가 Retain Count를 내릴 수 없는 상태가 되었음으로, classA와 B는 불사신이 되어버렸다.

profile
🍎Apple Developer Academy @ POSTECH 2nd, 🌱SeSAC iOS 4th

0개의 댓글