Swift ARC(Automatic Reference Counting)

Uno·2022년 11월 24일
0

Tip-Swift

목록 보기
21/26

서론

만약 사전에서 "뇽뇽" 이라는 단어가 있고, 우리가 그것을 검색했다고 가정합시다. 그런데 다음과 같은 결과가 나타납니다.

뇽뇽이란, "교교하다" 라는 뜻이다.

그런데 "교교하다" 라는 뜻을 또 모르겠습니다. 그래서 "교교하다" 라는 단어를 사전에서 검색합니다.

교교하다란, "뇽뇽하다" 라는 뜻이다.

???
이런식으로 서로가 서로를 참조하게되면, 우리는 사전을 찾게되면, 무한루프에 빠지겠죠. 계속 뇽뇽을 보고 교교를 찾아가고 교교를 찾으면 뇽뇽을 찾아봐야하고,

민약, 이게 Swift Class 에서도 발생하면, 어떤일이 발생할까요? 역시 무한 루프가 발생하겠죠. 사전은 그냥 그 사전을 버리면됩니다. 그런데 앱에서 이런일이 발생하면, 메모리를 계속 잡아먹게되고(Memory leak) 문제가 커지면 앱이 꺼지게 될 수도 있습니다.

이런 상황에 대해서 설명해줄 수 있는 개념이 "ARC" 이고, 이것에 대해서 이번에 알아보겠습니다.

ARC 란?

공식문서에서의 정의

공식문서의 첫 문장에 다음과 같이
적혀있습니다.

Swift ueses Autimatic Reference Counting(ARC) to track and manage your app's memory usage.

Swift 에서는 앱의 메모리 관리를 ARC 를 이용해서 한다고 합니다.

그리고 ARC 에 대해서, 이해하기 쉽게 다시 한 번 설명해줍니다.

This means that memory management "just work" in Swift, and you don't need to think about memory management yourself.

메모리 관리는 Swift 가 해줄테니, 너굴맨 에게 맞기라구 개발자는 신경쓸 필요가 없다고 합니다.

그렇다고 진짜 신경안쓰면, 참사가 발생합니다. 그래서 바로 다음 문단에서 이렇게 작성하고 있습니다.

However, in a few cases ARC requires more information about the relationships between prats of your code in order to manage memory for you.

경우에 따라서는, 추가적인 정보를 요구한다고 적혀있는데, 그냥 어느정도의 관리는 필요하다는 뜻이죠. 다만 그게 특이 케이스에 한정된다는 의미로 생각됩니다.

정리

여기까지 정리하면, 다음과 같습니다.

  • ARC 가 메모리 관리르 어지간하면 다해준다.
  • 근데 특정 케이스에서는 Swift 에게 정보를 주면된다.
  • 그러므로, 특정 케이스를 암기하고, 해당 상황이 발생하면 그 방법으로 해결하면 된다.

ARC 동작 원리

공식문서엔 이렇게 작성되어 있습니다.

Every time you create a new instance of a class, ARC allocates a chunk of memory to store information about that instance.

class 인스턴스를 생성할 때마다, 메모리에 ARC 가 해당 정보를 할당합니다. 여기서 중요한 부분은 "Class Instance" 라는 점 입니다. 그러면, Class 가 아니면 ARC 가 동작 안한다는 뜻이니까요.

공식문서에도 해당 부분을 언급하고 있습니다. 오직 Class 에서만 동작한다구요.

Reference counting applies only to instances of classes. Structures and enumerations are value types, not reference types, and aren't stored and passed by reference.

참조 카운팅은 오직 클래스 인스턴스에서만 동작하고, Structures , Enumerations 과 같은 Value 타입은 참조하지 않습니다. 즉, 저장되지도 않고 전달하지도 않는다는 뜻이죠.

cf) Value Type VS Reference Type
Value Type: 새로운 변수에 특정 Value Type 데이터를 할당하면, 그 값을 복사한다.
Reference Type: 새로운 변수에 특정 Value Type 데이터를 할당하면, 그 값을 복사하지 않고, 포인터를 전달한다.
(자세한 내용은 링크 를 참조)

이어서, ARC 설명하겠습니다.
ARC 는 정보를 할당하기도 하지만, 해제하기도 합니다. 해제는 해당 클래스 인스턴스를 참조를 아무 객체도 하지 않을 경우 입니다. 이게 무슨 말이나면, 다음 코드를 봅시다.

class Menu {
	let name: String
	let price: String

	init(name: String, price: String) {
		self.name = name
		self.price = price
	}

	deinit {
		print("DEBUG: \(name) 메뉴 사라집니다...")
	}
}
  • Menu 라는 Class 가 있으므로, 해당 객체를 어디서 참조하면, ARC 를 통해 참조 횟수를 셀겁니다.
var 소세지야채볶음: Menu?
var 소야: Menu?
var 소세지소세지볶음: Menu?

소세지야채볶음 = Menu(name: "쏘야", "1000")
소야 = 소세지야채볶음
소세지소세지볶음 = 소세지야채볶음
  • 지금 Menu(name: "쏘야", price: "1000") 이라는 클래스 인스턴스가 "소세지야채볶음" 입니다.
  • 그리고 그 값이 소야소세지소세지볶음 에 할당되었습니다.
  • 이 때, "소세지야채볶음" 은 Reference Type 이므로, 총 3 번 참조가 발생했습니다. (ARC 동작)

그런데, 다음코드가 이렇게 왔습니다.

소야 = nil
소세지소세지볶음 = nil
소세지야채볶음 = nil

그러면 모든 참조 횟수가 없어지겠죠. 그러면 더이상 Class Menu 를 참조하는 건 없습니다. ARC 동작원리 상, 이부분에서 메모리에서 단어 뜻 그대로 freeUp 되어야 합니다.

Additionally, when an instance is no longer needed, ARC frees up the memory used by that instance so that the memory can be used for other purpose instead.

지금과 같이 참조하는 것을 "Strong Reference" 라고 칭합니다.
스트롱??

The reference is called a "strong" reference beacuse it keeps a firm hold on that instance, and doesn't allow it to be deallocated for as long as that strong reference remains.

  • 이유는, 참조하고 있는 객체가 사라지지 않는다면, 해당 참조를 제거하지 않는 다는 뜻 입니다.

정리

  • 클래스 인스턴스를 참조하면, Counting 이 참조 수 만큼 상승한다.
  • 참조수가 0 이 되면, 사라진다.
  • 일반적인 경우, 위 참조를 "Strong Reference" 라고 칭한다.

예시

공식문서상에 있는 예시 말고, UIKit 에서 구현할 때의 상황을 가정해보겠습니다.

final class ViewController: UIViewControlelr {
	let secondViewController: SecondViewController
	var someString = String()

	init(secondVC: SecondViewController) {
		self.secondViewController = secondVC
		super.init(nibname: nil, bnudle: nil)
	}	

	required init?(coder: NSCoder) {
		fatalError("init(coder:) has not been implemented")
		// or super.init(coder: corder)
	}

	override func viewDidLoad() {
		super.viewDidLoad()
	}
}
  • ViewController 에서 SecondViewController 를 초기화 함수에서 의존성을 주입받습니다.
    (의존성 주입 = 필요한 값을 전달 받는다.)

SecondViewController 에서 ViewController 의 값을 사용하는 상황입니다.

final class SecondViewController: UIViewController {
	let viewController: ViewController

	init(viewController: ViewController) {
		self.viewController = viewController
	}

	required init?(coder: NSCoder) {
		fatalError("init(coder:) has not been implemented")
	}

	override func viewDidLoad() {
		super.viewDidLoad()
	}

	// cf) Action 등록 코드는 생략합니다.
	public func buttonDidTap() {
		...
		viewController.someString = "값 전달"
	}
}

ViewController 는 SecondViewController 인스턴스를 가지고 있습니다.

  • SecondViewController 의 참조 카운팅 +1

SecondViewController 는 ViewController 의 인스턴스를 가지고 있습니다.

  • ViewController 의 참조 카운팅 +1
    (사실 SceneDelegate 에서도 이미 소유하고 있기 때문에 현재 2 입니다.)

현재 코드의 동작순서는 다음과 같습니다.
ViewController 화면에서 SecondViewController 화면으로 전환되겠죠. 그래서 버튼을 누르면(buttonDidTap) ViewController 로 값이 전달됩니다.

SecondViewController 가 Window 상에서 사라진다고 해도 ViewController 가 살아 있으면, 메모리에서 제거되지 않습니다. (인스턴스가 있기때문)

그러면, ViewController 가 사라지면 어떻게될까요?

ViewController 가 사라지면, 상식적으로는 메모리에서 제거되야할 것 같지만, 사라지지않은 SecondViewController 가 ViewController 를 바라보고 있습니다. 그러므로 사라질 수 없습니다. 즉, 서로가 서로를 참조하고 있는 상황이라서 화면에서는 안보이지만, 메모리상으로는 남아있습니다. 이것을 공식문서에서는 다음과 같이 말합니다.

Unfortunately, linking these two instances creates a strong reference cycle between them.

strong reference cycle 에 빠진상태인거죠.
위 예시가 이해가 안되시면, 공식문서 예시를 참조해보세요.

그러면, 어떻게 하면 되나?

4 개의 알파벳이면 가능합니다.

final class SecondViewController: UIViewController {
	weak var viewController: ViewController!
	...
}
  • 기존의 let 을 -> weak var 로 변경해주었습니다.
  • 이렇게 되면, Swift 에게 "이것은 강한참조를 하게되면, 사이클에 걸리니, 약한 참조로해라" 라고 말해주는 겁니다.

그렇게되면, ViewController 가 사라질 때, SecondViewController 에서 참조하는 값은 '약한' 참조이므로, 해당 값을 제거하게됩니다. (누가? ARC 와 Swift 가)

요약

  • ARC 는 메모리를 자동으로 관리해주는 Swift 의 도구이다.
  • 참조할 때마다(Class Instance를) 카운팅이 하나씩 늘어난다. 이 때, 문제가 되는 것은 Strong Reference 이다.
  • 특정 상황에서는 ARC에게 맥락을 알려줘야하는데, 그 때 사용하는 키워드중 하나가 weak 이다.

감사합니다.

참고자료

profile
iOS & Flutter

0개의 댓글