[iOS]Strong과 weak 참조 방식

메모리 참조 방식을 살피기 전에 ARC를 짚고 넘어가보려고 합니다.

ARC ❓

정의 ▾

  • 컴파일 시 코드를 분석해서 자동으로 retain, release 코드를 생성해주는 것
    • retain
      • retain count(= reference count) 증가를 통해 현재 Scope에서 객체가 유지되는 것을 보장합니다.
    • release
      • retain count(= reference count) 감소시킵니다.
      • retain 후에 필요 없을 때 release 합니다.
  • 참조된 횟수추적더 이상 참조되지 않는 인스턴스를 메모리에서 해제해 주는 것
    • 참조 카운트가 0이 되면 자동으로 메모리 해제된다고 보면 됩니다.
    • 0이 될 때 deinit을 호출해서 메모리 해제를 시킵니다.
  • 자동으로 RC를 관리해주기 때문에 메모리 해제에 대한 개발자의 부담을 덜어준다고 합니다.
    • Objective-C에서는 MRC, 수동으로 메모리 관리를 했다고 합니다.
  • Swift Runtime이라는 library에 구현되어 있습니다.

 

왜 존재하는가 

  • ARC는 Heap 영역과 관련이 있는데 이 Heap은 class, closure 등의 참조형 자료들이 머무는 공간이며, 개발자가 동적으로 할당하는 메모리 공간이기 때문에 관리가 필요합니다.
  • 관리는 Heap 영역에 참조형 자료가 얼마나 참조되고 있는지 카운팅하고 이에 따른 메모리를 할당 및 제거를 하면 됩니다.
    이를 자동으로 해주는 것이 ARC 입니다.

 

Strong 🍎

정보 ▾

  • 해당 인스턴스의 소유권을 가집니다.
  • 자신이 참조하는 인스턴스의 retain count를 증가시킵니다.
  • 값 지정 시점에 retain이 되고, 참조가 종료되는 시점에 release가 됩니다.
  • 선언할 때 아무것도 적어주지 않으면 default로 strong이 됩니다.
  • 강한(strong) 참조의 규칙을 모르고 사용하게 되면 메모리 누수(Memory Leak)가 발생할 수 있습니다.
    • 위 문제를 발생시키는 원인이 "순환 참조" 입니다.
    • 위 해결책이 바로 "약한참조(weak, unowned)" 입니다.

 

아래 순환 참조를 통해 Strong의 예시를 살펴보겠습니다.

 

순환 참조 → 메모리 누수 ❓

일단 아래 예시를 살펴보겠습니다.

class Man {
    var name: String
    var girlfriend: Woman?
    
    init(name: String) {
    	self.name = name
    }
    deinit { print("Man deinit..") }
}

class Woman {
    var name: String
    var boyfriend: Man?
    
    init(name: String) {
    	self.name = name
    }
    deinit { print("Woman deinit..") }
}
  • Man에는 Woman 타입의 프로퍼티(girlfriend)가 있습니다.
  • Woman에는 Man 타입의 프로퍼티(boyfriend)가 있습니다.

 

  • 인스턴스 생성
var cheolsu: Man? = .init(name: "철수")
var yeonghee: Woman? = .init(name: "영희")
  • 메모리는 다음과 같이 표현이 됩니다.

 

  • 만약 철수와 영희가 사귀면 어떻게 될까?
chelsu?.girlfriend = yeonghee
yeonghee?.boyfriend = cheolsu
  • 메모리는 아래와 같이 표현이 됩니다.

  • Man 인스턴스와 Woman 인스턴스의 RC가 1씩 증가했습니다.
    • boyfriend 프로퍼티는 Man 인스턴스의 주소값이 할당
    • girlfriend 프로퍼티는 Woman 인스턴스의 주소값이 할당
    • 두 프로퍼티는 default strong이기 때문에 RC 값이 1씩 증가해버립니다.
  • 위 예시처럼 두 개의 객체가 서로가 서로를 참조하고 있는 형태순환 참조

 

여기서 문제 ❗️

  • 만약 철수와 영희가 헤어지게 되면 어떻게 될까?
cheolsu = nil
yeonghee = nil
  • 인스턴스를 가리키던 변수에 nil을 대입했으니 RC가 1만큼 감소하고, 0일 경우 메모리에서 해제 되어야 합니다.
  • 하지만 위 코드 결과로는 deinit이 호출되지 않습니다.
    • 철수와 영희가 가리키던 인스턴스가 힙에서 사라지지 않고, 계속 메모리를 차지 않고 있다는 뜻!
      • 아까 순환참조로 인해 서로의 RC가 1씩 증가했기 때문

 

weak 🥝

정보 ▾

  • 해당 인스턴스의 소유권을 가지지 않고, 주소값만 가지고 있는 포인터 개념입니다.
  • 자신이 참조하는 인스턴스의 retain count를 증가시키지 않습니다.
    • release도 발생하지 않게 됩니다.
  • 자신이 참조는 하지만 weak 메모리를 해제시킬 수 있는 권한은 다른 클래스에 있습니다.
  • 메모리가 해제될 경우 자동으로 레퍼런스가 nil로 초기화를 해줍니다.
  • weak 속성을 사용하는 객체는 항상 optional 타입이어야 합니다.
    • 해당 객체가 nil일 수 있기 때문입니다.

 

아래 예시를 통시 weak를 살펴보겠습니다.

 

순환참조 해결 예시 ▾

class Man {
    var name: String
    weak var girlfriend: Woman?
    
    init(name: String) {
    	self.name = name
    }
    deinit { print("Man deinit..") }
}

class Woman {
    var name: String
    var boyfriend: Man?
    
    init(name: String) {
    	self.name = name
    }
    deinit { print("Woman deinit..") }
}
  • 순환 참조를 일으키는 프로퍼티 앞에 weak를 붙여줍니다.

 

  • 인스턴스 생성
var cheolsu: Man? = .init(name: "철수")
var yeonghee: Woman? = .init(name: "영희")

cheolsu?.girlfriend = yeonghee
yeonghee?.boyfriend = cheolsu
  • 위 처럼 인스턴스를 생성하면 아래와 같이 메모리를 표현할 수 있습니다.

  • girlfriend의 프로퍼티가 weak라서 Woman 인스턴스의 주소값을 할당받지만, Woman 인스턴스의 RC 값을 건들지 않습니다.

 

  • 메모리 해제
cheolsu = nil
yeonghee = nil

 

메모리 해제 순서 📝

  • 철수, 영희 변수는 nil이 할당된 순간 각각의 인스턴스에 대한 RC 값이 1씩 감소합니다.
  • RC가 0이 된 Woman 인스턴스가 메모리에서 해제됩니다.
  • weak로 선언된 girlfriend가 참조하던 인스턴스가 메모리에서 해제 되었으니, girlfriend의 값이 nil로 할당됩니다.
    • weak 특징 → 가리키던 인스턴스가 메모리에서 해제될 경우 nil이 할당됩니다.
  • Man 인스턴스의 RC 값도 0이 되었으니 메모리에서 해제됩니다.
  • deinit 함수가 작동 합니다.

 

 

Unowned 🍊

정보 ▾

  • 강한 순환 참조를 해결할 수 있습니다. (weak와 동일)
  • RC 값을 증가시키지 않습니다. (weak와 동일)
  • 인스턴스를 참조하는 도중에 해당 인스턴스가 메모리에서 사라질 일이 없다고 확신하는 것이 핵심입니다. (weak와 차이)
    • 참조하던 인스턴스가 메모리에서 해제되도, nil을 할당받지 못하고 해제된 메모리 주소값을 계속 갖고 있습니다.
  • 주로 객체의 라이프사이클이 명확하고 개발자에 의해 제어 가능이 명확한 경우 weak Optional 타입 대신 사용하여 더 간결한 코딩을 가능하게 합니다.

 

 

참고 사이트 ▾

https://devsrkim.tistory.com/entry/Swift-%EB%A9%94%EB%AA%A8%EB%A6%AC%EB%A5%BC-%EC%B0%B8%EC%A1%B0%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-Strong-Weak-Unowned

 

[Swift] 메모리를 참조하는 방법 (Strong, Weak, Unowned)

* ARC(Automatic Reference Counting) 란? - 컴파일 시 코드를 분석해서 자동으로 retain, release 코드를 생성해주는 것. - 참조된 횟수를 추적해 더 이상 참조되지 않는 인스턴스를 메모리에서 해제해 주는 것.

devsrkim.tistory.com

https://babbab2.tistory.com/27

 

iOS) 메모리 관리 (2/3) - strong , weak, unowned, 순환 참조

안녕하세여 소들입니다! 저번 포스팅이 길어져서 한번 끊고 가봅니다 :) 뭐 흐름 상 끊어도 될만한 부분이었어서.. ~_~ 이번에 공부할 내용은  뭐 제목에서 써놓은 것처럼 strong weak unowned 순환 참

babbab2.tistory.com

 

'iOS_Swift.zip' 카테고리의 다른 글

[Swift]SnapKit + Then CollectionView Cell Size  (0) 2022.09.10
[Swift]Status Bar 색상 변겅  (0) 2022.04.21
[iOS]Protocol  (0) 2022.02.20
[iOS]NotificationCenter  (0) 2022.02.18
[iOS]App Thinning  (0) 2022.02.16