본문 바로가기
iOS/Memory & Optimizing

[iOS] Strong Reference Cycle - 객체 참조

by Mildwhale 2019. 11. 19.

이번 글에서는, 이전 글의 마지막에 언급했던 Strong Reference Cycle에 대해서 알아보려 합니다.

이전에 작성했던 ARC에 대한 글을 읽어보시면, 이해하는데 도움이 됩니다.

 

[iOS] 메모리 관리 기법 - ARC (Automatic Reference Counting)

Xcode 4와 동시에 등장한 ARC(Automatic Reference Counting) 덕분에, iOS 개발자들은 레퍼런스 카운트 관리에서 해방될 수 있었습니다. 그렇다면 ARC는 어떻게 동작하기에 개발자들의 수고를 덜어주고 있을까요?..

mildwhale.tistory.com


이전 글에서 ARC가 객체의 참조 카운트를 추적하고, 더 이상 필요하지 않을 때(카운트가 0) 객체가 메모리에서 해제된다고 했습니다. 하지만, 프로그래밍할 때 의도치 않게 객체가 메모리에서 해제되지 않는 상황이 발생할 수 있습니다.

어떤 상황에서 메모리가 해제되지 않는지, 그리고 어떻게 해결해야 하는지 한번 알아보도록 하겠습니다.

객체 간의 Strong Reference Cycle

이해를 돕기 위해 간단한 예제를 준비했습니다.

위 코드를 보면 Person과 Apartment 클래스가 정의되어 있습니다. 

위에 정의된 클래스를 사용하여, 아래 코드를 실행하면 객체 간의 관계는 어떻게 그려질까요?

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

john.apartment = unit4A
unit4A.tenant = john

1 - Strong

Person 인스턴스는 john이라는 변수와, unit4A 인스턴스의 tenant가 참조하게 되고, Apartment 인스턴스는 unit4A 변수와, john 인스턴스의 apartment에서 참조하고 있는 것을 볼 수 있습니다.

이후, 각 인스턴스를 메모리에서 해제하고 싶어 아래의 코드를 실행해봅니다. 과연 인스턴스는 메모리에서 해제되었을까요?

john = nil
unit4A = nil

2 - Strong (Memory Leak)

안타깝게도 인스턴스는 메모리에서 해제되지 않을 것입니다. 왜냐하면, 변수와 인스턴스 간의 레퍼런스는 사라졌지만, 각 인스턴스간의 참조가 여전히 남아있어서, Reference Count가 0이 될 수 없기 때문이죠.

그리고, 해당 인스턴스에 접근할 방법이 없어졌기 때문에, 메모리 누수가 발생하게 됩니다.


위 클래스 구조는 어떤 것이 문제였을까요? 바로 객체 간의 강한(Strong) 참조 때문입니다.

Swift에서는 2가지 방법을 이용하여 이 문제를 해결할 수 있습니다. 이 글에서는 Weak Reference를 이용한 해결 방법에 대해서 알아보겠습니다.

해결 방법 == Weak Reference

아래 코드를 보면 Apartment 클래스의 tenant 변수앞에 weak라는 단어가 추가되었습니다. 이 것은 해당 변수의 속성을 weak로 지정해줍니다. (별도의 선언이 없으면 기본적으로 Strong으로 동작합니다)

weak로 선언된 변수는 인스턴스의 레퍼런스 카운트를 증가시키지 않습니다.

weak 속성이 정의된 클래스를 사용하여, 아래 코드를 실행하면 객체 간의 메모리 참조는 어떻게 구성될까요?

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

john.apartment = unit4A
unit4A.tenant = john

3 - Weak Reference가 적용 된 모습

첫 번째 그림과의 차이점이 눈에 보이시나요? 

Apartment의 tenant 변수가, Person 인스턴스를 weak 레퍼런스로 참조하는 것을 볼 수 있습니다. 즉, Apartment 인스턴스가 Person 인스턴스를 참조해도, Person 인스턴스의 레퍼런스 카운트는 증가하지 않게 됩니다.

Person 인스턴스를 메모리에서 해제하기 위해 아래 코드를 실행하면, 인스턴스가 메모리에서 해제되며 deinit에 정의된 문구가 Print 되는 것을 볼 수 있습니다.

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

4 - 메모리에서 해제 된 Person 인스턴스

위 그림을 보면 Person 인스턴스가 메모리에서 해제되었고, Apartment 인스턴스를 unit4A 변수가 참조하고 있는 것을 볼 수 있습니다.

마지막으로 unit4A 인스턴스를 메모리에서 해제하기 위해 아래 코드를 실행하면, Apartment 인스턴스가 메모리에서 해제되며 deinit이 호출되는 것을 볼 수 있습니다.

unit4A = nil
// Prints "Apartment 4A is being deinitialized"

5 - 모든 인스턴스가 메모리에서 해제 됨


마무리

이번 글에서는 객체 간의 Strong Reference Cycle에 대해 알아보았습니다. 클래스 간의 참조에 대해 충분히 고민하지 않거나, 단순 실수로 인해 흔히 발생할 수 있는 문제이지만 손쉽게 해결이 가능합니다.

Strong Reference Cycle 문제는 아직 하나의 케이스가 남아있습니다. 다음 글에서는 Strong Reference Cycle이 발생하는 또 다른 원인과, 해결 방법에 대해 알아보도록 하겠습니다.

참고자료: Automatic Reference Counting