메모리 참조(Strong, Weak, Unowned)

유재우·2023년 2월 8일
0

iOS Developer의 길!

목록 보기
10/15

메모리 참조란?

iOS에선 ARC를 활용하여 앱의 메모리 사용을 추적하고 관리한다.
ARC는 객체에 대한 강한 참조가 남아있는 한 해당 객체는 메모리 해제가 되지 않는다.

강한 참조만 사용한다면 일일이 메모리 해제에 신경을 써야하고 그러지 못하게 되면 엄청난 메모리 낭비가 될것이다.
그래서 iOS에선 weak, unowned를 이용해 더 효율적으로 메모리를 관리할 수 있다.

ARC란?
힙에 할당된 인스턴스가 사용되지 않다고 판단 될 때에 자동으로 메모리를 해제해주어 관리해주는 프로세스


Strong (강한 참조)

객체를 참조하고 reference count를 증가 시킨다.

예제)

var chamjo: Man? = Man(age: 25) // 강한 참조
chamho = nil 					// 메모리 해제

하지만 여기서 두 개의 객체가 상호 참조하는 경우와 같은 강한 순환 참조가 만들어 질 수 있다.
이 경우엔 참조 횟수가 0이 되지 못하여 메모리 누수가 발생한다.

예제)

class Strong {
	var string: Strong? = nil // 강한 참조 객체
}
var chamjo1: Strong? = Strong() // 객체 변수 1
var chamjo2: Strong?: Strong() // 객체 변수 2
chamjo1?.strong = chamjo2 // chamjo2 참조
chamjo2?.strong = chamjo1 // chamjo1 참조, 서로 강한 참조 = 강한 순환 참조
chamjo1 = nil // 객체 변수 메모리 해제
chamjo2 = nil // 객체 변수 메모리 해제

두개의 객체 변수는 마지막에 nil을 넣어 메모리가 해제되었지만 각각의 강한 참조가 되었기 때문에 메모리 해제가 되지 못했다.
즉 객체인 strong은 nil을 넣어주지 못하고 객체 변수의 메모리를 해제하였다는 뜻이다.
이 때 객체에 접근할 방법도 없고 메모리를 해제할 방법도 없으니 메모리 누수가 일어나고 있다.

이런 강한 순환 참조를 해결하는 방법에는 약한 참조(weak reference)와 미소유 참조(unowned reference) 두 가지가 있다.


Weak (약한 참조)

객체를 참조하지만 reference count의 변화는 없다.
  • 해당 인스턴스의 소유권을 가지지 않고, 주소 값만을 가지고 있는 포인터 개념이다.
  • 자신이 참조하는 인스턴스의 reference count를 증가시키지 않는다.
  • 자신이 참조는 하지만 weak 메모리를 해제시킬 수 있는 권한은 다른 클래스에 있다.
  • 메모리가 해제될 경우 자동으로 레퍼런스가 nil로 초기화를 해준다.
  • weak 속성을 사용하는 객체는 항상 옵셔널 타입이어야 한다. (해당 객체가 nil일 수 있기 때문)

예제)

weak var chamjo: Man? = Man(age: 25) // 약한 참조
// 약한 참조이므로 바로 객체가 해제되어 nil이 된다.

약한 참조는 강한 순환 참조를 해결하기 위해 사용되는 가장 보편적인 방법이다.
주로 인스턴스의 생명주기가 짧을 때 사용하고 참조하고 있는 인스턴스를 강하게 유지하지 않기 때문에, 약한 참조로 참조하고 있는 동안에 인스턴스가 메모리 해제되는 것이 가능하다.


Unowned (미소유 참조)

객체를 참조하지만 reference count의 변화는 없다.
  • 해당 인스턴스의 소유권을 가지지 않는다.
  • 자신이 참조하는 인스턴스의 reference count를 증가시키지 않는다.
  • nil이 될 수 없다. 옵셔널로 선언되어선 안된다.

약한 참조와는 다르게 다른 인스턴스와 같은 생명주기를 가지거나 더 긴 생명주기를 가질 때 사용한다.
또한, 가장 큰 차이점은 옵셔널에 사용하지 못한다.
따라서 nil값을 가질 수 없고 항상 값을 가지고 있어야 한다.

참조하는 객체의 참조횟수가 0이되어 메모리가 해제되는 경우 약한 참조에서는 참조 값이 nil로 대체되지만 미소유 참조에선 참조 값이 그대로 유지된다.


Weak vs Unowned

  • weak는 객체를 계속 추적하면서 객체가 사라지면 nil로 바꾼다.
  • unowned는 객체가 사리지게 되면 댕글링 포인터가 남는다.

댕글링 포인터란 (Dangling Pointer)?

원래 바라보던 객체가 해제되면서 할당되지 않는 공간을 바라보는 포인터


어떨 때 쓰이나요?

Strong

레퍼런스 카운트를 증가시켜 ARC로 인한 메모리 해제를 피하고, 객체를 안전하게 사용하고자 할 때 사용한다.

Weak

대표적으로 Retain Cycle에 의해 메모리가 누수되는 문제를 막기 위해 사용되며, delegate 패턴이 있다.

Unowned

객체의 라이프사이클이 명확하고 개발자에 의해 제어 가능이 명확한 경우에 weak Optional 타입 대신 사용하여 조금 더 간결한 코딩이 가능하다.

약한 참조가 필요한 경우 weak 키워드만을 사용하고, guard let(Or if let) 구문을 통해 안전하게 옵셔널을 추출하는 것을 권장한다고 한다.


순환참조

서로가 서로를 소유하고 있어 절대 메모리 해제가 되지 않는 것

ARC가 편하게 메모리 관리를 해주지만 자칫하면 순환참조가 발생할 수 있다.

예) delegate 패턴
delgate를 하기위해서 일을 시키는 객체와 일을 하는 객체 두 개가 무조건 있어야 하는데,
아래의 코드로 객체를 연결시켜줌으로 인해 첫번째 뷰와 두번째 뷰는 서로를 소유하는 상황이 된다.

vc.delegate = self

즉 첫번째 뷰에서 두번째 객체를 만듦으로 두번째 뷰를 소유하고, 두번째 뷰의 delegate를 첫번째 뷰로 연결해줌으로써 첫번째 뷰를 소유하는 순환참조가 된다.


이를 해결하기 위해서 두번째 뷰의 delegate에 weak을 붙여주면 된다.

weak var delegate: FirstView?

이렇게 되면 첫번째 뷰만 두번째 뷰를 소유하기 때문에 순환참조가 발생하지 않는다.

참고한 블로그 링크

profile
끝없이 탐구하는 iOS 개발자 유재우입니다!

0개의 댓글