-
Swift4 : 자동 참조 카운팅 : #Automatic Referece Counting : #ARC :#강한참조 : #Strong Reference Cycle : #메모리 누수스위프트: Swift/스위프트: 언어자체: 문법 2018. 9. 10. 19:38
안녕하세요 ! 씩이 입니다!
저는 Swift 와 iOS 를 공부하고 연구하는 학생입니다.
같은 분야를 공부하는 분들에게 조금이라도 도움이 주고 싶어서 공부하는 것들을 공유합니다.
제 3자가 있다고 가정하고 설명하기 때문에 존대를 하지 않는점 이해 부탁드립니다.
공유가 미래 라고 생각합니다.
한국의 모든 개발자분들 존경합니다!
- Swift version : Swift 4.2 Swift 언어
- 참고한 것들
- 씩이 Github
- 자료구조 소스파일 있습니다.
- iOS 관련 자료들, 정보들 정리해 두었습니다.
- 스위프트로 구현한 자료구조 : DataStructures in Swift4
- Swift4 : 연결리스트 (1 / 3) : #LinkedList : #DataStructrue : #자료구조
- Swift4 : 연결리스트 (2 / 3) : #LinkedList : #값 추가하기, push, append : #값 삽입하기,insert
- Swift4 : 연결리스트 (3 / 3) : #LinkedList : #값 제거하기, pop, removeLast, remove(at: )
- [스위프트 : 자료구조] 스택: Stack: 자료구조: DataStructure: 쌓기
- [스위프트 : 자료구조] 스택 : Stack : 프로토콜 지향 스택 구현하기
- [스위프트 : 자료구조] 큐 (1 / 4): Queue: #자료구조: #배열로 구현한 큐: #배열의원리
- [스위프트 : 자료구조] 큐 (2 / 4): Queue: #자료구조: #연결리스트: #더블연결리스트: #DoublyLinkedList
- [스위프트 : 자료구조] 큐 (3 / 4): Queue: #자료구조: #Stack으로 구현: #더블스택: #DoubleStack: #제일좋음
- [스위프트 : 자료구조] 큐 (4 / 4): Queue: #자료구조: #RingBuffer: #링버퍼로 구현한 큐: #고정된배열: #마지막!!
- [스위프트 : 자료구조] 큐: Queue: 프로토콜 지향 큐 구현하기
- 스위프트: 트리: Tree: #자료구조: #깊이우선탐색: #레벨정렬탐색: #검색알고리즘: Swift4
- 스위프트: 이진 탐색 트리(1 / 2): #BinarySearchTree: #자료구조: #배열과 비교: #트리: #탐색: #삽입: #삭제
- 스위프트: 이진 탐색 트리(2 / 2): #BinarySearchTree: #자료구조: #배열과 비교: #트리: #탐색: #삽입: #삭제
- [스위프트 : 자료구조] AVL Tree: 자가 균형 트리: #balance: #트리의 높이: #rotation메소드: #성능오짐
- [스위프트:자료구조] 트라이: Trie: 문자열 찾기: 단어 찾기
- [스위프트:자료구조] Heap: 힙 자료구조 (1 / 2) : Heap 이란?
- [스위프트:자료구조] Heap: 힙 자료구조 (2 / 2) : Heap 구현하기
- 스위프트로 구현한 알고리즘 : Algorithms in Swfit4
- [스위프트 : 알고리즘] 재귀호출 (1 / 6) : recursive: 재귀호출 : 재귀함수: 반복문: 팩토리얼: 거듭제곱: 피보나치: 하노이의 탑: 최대공약수
- [스위프트 : 알고리즘] 재귀 : 팩토리얼 (2 / 6) : factorial: 재귀호출 : 재귀함수: 반복문: 팩토리얼: 거듭제곱: 피보나치: 하노이의 탑: 최대공약수
- [스위프트 : 알고리즘] 재귀 : 거듭제곱 (3 / 6) : Power: 재귀호출 : 재귀함수: 반복문: 팩토리얼: 거듭제곱: 피보나치: 하노이의 탑: 최대공약수
- [스위프트 : 알고리즘] 재귀 : 피보나치 수열(4 / 6) : Fibonacci: 재귀호출 : 재귀함수: 반복문: 팩토리얼: 거듭제곱: 피보나치: 하노이의 탑: 최대공약수
- [스위프트 : 알고리즘] 재귀 : 하노이의 탑 (5 / 6) : Hanoi: 재귀호출: 재귀함수: 반복문: 팩토리얼: 거듭제곱: 피보나치: 하노이의 탑: 최대공약수
- [스위프트 : 알고리즘] 재귀 : 최대공약수 (6 / 6) : GCD: 재귀호출: 재귀함수: 반복문: 팩토리얼: 거듭제곱: 피보나치: 하노이의 탑: 최대공약수
- [스위프트:알고리즘] 이진 탐색[1 / 3]: Binary Search: 이진 탐색이 뭐야?
- [스위프트:알고리즘] 이진 탐색[2 / 3]: Binary Search: 이진 탐색: 반복문, 재귀호출로 구현하기
- [스위프트:알고리즘] 이진 탐색[3 / 3]: Binary Search: 이진 탐색: 프로토콜 지향으로 구현하기
- Swift 주제별 분류
- Swift4 : 제어 전달 명령문( Control Transfer Statement ) : #continue, #break, #return 키워드
- Swift4 : 클래스와 구조체 : #값을 대하는 방식 : #참조타입, 값 타입 : #===
- Swift4 : 프로퍼티 : #Property : #get, set : #willSet, didSet
- Swift4 : 메소드 : #Method : #영향력 범위 : #self : #mutating : #값타입 수정
- Swift4 : 프로토콜 1 : #Protocol : #설계 : #요구사항 : #델리게이트 패턴 전처리 (1 / 2)
- Swift4 : 프로토콜 2 : #델리게이트 패턴 : #델리게이션 (2 / 2)
- Swift4 : 제네릭 : #Generics : #왜필요해? : #where키워드 : #제약사항걸기
- Swift4 : 자동 참조 카운팅 : #Automatic Referece Counting : #ARC :#강한참조 : #Strong Reference Cycle : #메모리 누수
- Swift4 : 클로저: Closure: #표현방식: #왜필요해?: #효율적: #간결성: #생략
- [스위프트 : 기초] 서브스크립트 : Subscript : 지름길
자동 참조 카운팅 ( Automatic Reference Counting ) : ARC
- What is ARC
- 스위프트는 자동참조카운팅( ARC )을 메모리 사용을 관리하고 추적하기 위해 사용합니다.
- 대부분의 경우 메모리 관리는 ARC가 알아서 하고, 우리는 메모리 관리를 신경쓸 필요가 없습니다.
- 이 글의 목적은 이 과정이 어떻게 수행되는지에 대해 알아보는 것.
- ARC 는 클래스 인스턴스 가 더이상 필요하지 않을 때 자동으로 그 인스턴스가 사용하는 메모리를 제거합니다.
- 참조 카운팅은 클래스의 인스턴스에만 적용됩니다. 구조체와 열거형은 참조 타입이 아니라 값 타입이라 해당사항이 없습니다.
- 어떻게 작동하는 거야?
- 클래스의 새로운 인스턴스를 생성할 때 마다 ARC 는 인스턴스의 정보를 저장하기 위해서 메모리를 할당합니다.
- 이 메모리에는 해당 인스턴스와 연결된 모든 저장 프로퍼티의 값과 함께 인스턴스의 타입에 대한 정보가 저장됩니다.
- 인스턴스가 필요 없어질 때, ARC 는 메모리를 다른 목적으로 사용될 수 있게 하기 위해 그 인스턴스가 사용하는 메모리를 해제합니다. 이렇게 하면 클래스 인스턴스가 더 이상 필요하지 않을때 메모리 공간을 차지하지 않도록 합니다. ( 효율성 )
- ARC 는 해당 인스턴스에 대한 참조가 적어도 하나 이상 있다면, 인스턴스를 해제하지 않습니다.
- 이를 가능하게 하려면 클래스 인스턴스를 프로퍼티나 변수나 상수에 할당할 때마다, 그 프로퍼티나 변수나 상수는 강한 참조 ( strong reference )를 만들어야 합니다. ( 변수가 인스턴스를 꽉 붙잡고 있다고 생각하면 될듯. )
- 해당 인스턴스를 꽉 붙잡고 있으므로 strong reference 라고 하는 것. '강한 참조' 상태이면 해당 인스턴스의 메모리를 해제할 수 없습니다.
- 이해 안되죠? 소스에서 다 설명합니다!
- #강한참조 : #deinit : #메모리 해제
123456789101112131415161718192021222324252627282930313233class Person { // Person 클래스에는 이름과 초기화 상태를 나타내는 초기화 함수와 초기화 해제 함수가 있음.let name: Stringinit(name: String) {self.name = nameprint("\(name) is being initialized")}deinit { // 인스턴스가 메모리 해제될 때 메세지 출력하게 하는 deinitprint("\(name) is being deinitialized")}}// Person 클래스 타입의 세 변수 옵셔널로 생성. ARC 의 상태 변화 알아보기 위함.var reference1: Person? // 옵셔널 타입이므로 현재는 nil 로 초기화 되있음.var reference2: Person?var reference3: Person?reference1 = Person(name: "John Appleseed")// "John Appleseed is being initialized" 출력// Person 인스턴스의 메모리가 생기는 시점.// referece1 에서 새로운 Person 인스턴스에 대한 강력한 참조가 생깁니다.// 강한참조가 하나 이상 생겼기 때문에 ARC 는 이 인스턴스를 해제하지 않습니다.reference2 = reference1reference3 = reference1// 같은 인스턴스 참조를 2, 3 에 모두 할당하면?// 해당 인스턴스에 대한 강한 참조가 2개 더 생기는 것.// 그럼 현 시점에서는 Person 인스턴스에 대한 강한 참조는 3개 인 상태.reference1 = nilreference2 = nil// 2곳에 nil 할당해서 2개의 강한 참조를 끊으면?// 아직 1개, 마지막 강한 참조가 남아있으므로 ARC 는 Person 인스턴스 메모리를 해제하지 않아.reference3 = nil // "John Appleseed is being deinitialized" 출력.// ARC 가 Person 인스턴스 메모리 해제시키는 시점.// 마지막 참조가 남아있는 reference3 에도 nil 할당하면, 인스턴스 안쓰는걸로 알고 ARC 가 메모리 없애버림.cs - 클래스 인스턴스 간의 강한 참조 사이클 : Strong Reference Cycles
- 클래스 인스턴스가 참조가 0이 되야 메모리가 해제되는데, '강한 참조'가 0개가 되는, 즉 없어지는 시점에 도달하기 어려운 코드를 작성하는 경우가 있습니다.
- 이 문제는 두 클래스 인스턴스가 서로 강한참조로 묶여있을때, 각각의 인스턴스가 나머지 인스턴스를 놓지 않아서 참조가 0개가 되지 않게 합니다.
- 이는 메모리가 해제되지 않는 문제가 생깁니다.
- 어떻게 해결?
- 클래스 사이의 관계중 일부( 둘중 하나 ) 를 강한 참조를 하는 대신 약한( weak ) 혹은 소유하지 않게( unowned ) 참조로 정의해서 강한 참조 사이클 문제를 해결합니다.
- 예제에서는 "어떻게 강한참조사이클 문제가 생기게 되는지" 를 설명합니다.
- 12345678910111213141516171819// 강한 참조 사이클 문제가 어떻게 생기게 되는지를 볼 예정.class Person { // Person 클래스 : 이름과 사는 아파트 정보let name: Stringinit(name: String) { self.name = name }var apartment: Apartment? // 옵셔널.deinit { print("\(name) is being deinitialized") }}class Apartment { // Apartment 클래스 : 동과 , 거주하는 사람 정보let unit: Stringinit(unit: String) { self.unit = unit }var tenant: Person? // 옵셔널deinit { print("Apartment \(unit) is being deinitialized") }}var john: Person? // john 이라는 사람 , 옵셔널 이라 nil 초기화 되있겠쥬?var unit4A: Apartment? // 4A 동 아파트.john = Person(name: "John Appleseed") // 새로운 Person 인스턴스의 메모리가 생성되는 시점, john 에 강한참조 생성.unit4A = Apartment(unit: "4A") // 새로운 Apartment 인스턴스의 메모리 생성되는 시점, unit4A 에 강한참조 생성.
cs
- 현재상태
12345678john!.apartment = unit4A// john 에는 Person 인스턴스가 있죠? 이 인스턴스를 통해 apartment 프로퍼티에 접근해서 그곳에 unit4A 라는 Apartment 인스턴스를 할당한 것.// john.apartment 는 unit4A 에 있는 Apartment 인스턴스를 강한참조 함. => 문제// 의미적으론 존이 사는 아파트 정보에 unit4A 를 넣어준 것.unit4A!.tenant = john// 마찬가지로 tenant 에 john 할당.// unit4A.tenant 는 john 에 있는 Person 인스턴스를 강한참조 함. => 문제.cs - 현재 상태
// 자 이제 john 과 unit4A 에 nil 을 할당해서 deinit 수행되는지 보겠음. ( 메모리 해제되는지 보겠다는 것임. )
john = nil
unit4A = nil
// 둘 모두... 메모리 해제 안됨... 참조가 남아있는 것임. 이해안되면 본문에 그럼도 같이 올려놓음.
// 결국 메모리 '강한 참조 사이클' 문제 때문에.. 사용하지도 않는 메모리를 회수도 못함..ㅠㅠ
- 현재 상태
- 그림에서 보면, john 과 unit4A 에 할당되있는 메모리를 해제했지만, 내부적으로 강한참조가 연결되 있어서 사용하지도 않는 메모리를 해제하지 못해서 회수할 수 없음.
- 이 것을 '메모리 누수' 라고 하고, '강한 참조 사이클' 이 야기시키는 문제이다.
- '강한 참조 사이클' 해결 방법
- 두 가지 방법이 있어요. : weak 참조 와 unowned 참조
- 이 둘은 '강한 참조' 없이 두 인스턴스 서로를 참조가 가능하게 해줍니다!.
- weak Reference
- What is weak reference
- weak 참조는 참조하는 인스턴스를 강한 참조로 묶지 않고 참조 인스턴스를 처리함으로써 ARC 를 막지 않는다. 그렇게 '강한 참조 사이클'를 예방한다.
- weak 참조가 강한 참조를 설정하지 않기 때문에 weak 참조가 여전히 참조하고 있을 때에도 인스턴스가 메모리 해제될 가능성이 있다.
- ARC는 weak 참조가 참조하는 인스턴스가 메모리 해제되면 weak이 붙은 곳에 자동으로 nil 을 할당한다.
- weak 참조는 값이 runtime 환경에서 nil 로 바뀔 경우가 있기 때문에 항상 상수가 아닌 옵셔널 타입의 변수로 선언된다.
- 이해 안되죠? 소스코드로 봐야 합니다 ㅠㅠ. 위에서 했던 예제로 weak 적용해 보겠습니다!
1234567891011121314151617181920212223class Person { // Person 클래스 : 이름과 사는 아파트 정보let name: Stringinit(name: String) { self.name = name }var apartment: Apartment? // 옵셔널.deinit { print("\(name) is being deinitialized") }}class Apartment { // Apartment 클래스 : 동과 , 거주하는 사람 정보let unit: Stringinit(unit: String) { self.unit = unit }weak var tenant: Person? // weak 참조를 사용하기 위한 weak 키워드.deinit { print("Apartment \(unit) is being deinitialized") }}// 강한 참조 사이클에 갇혔던 경우. 아래.var john: Person?var unit4A: Apartment?john = Person(name: "John Appleseed")unit4A = Apartment(unit: "4A")john!.apartment = unit4Aunit4A!.tenant = john // 이 시점에서 tenant 가 weak 참조이기 때문에 john 을 약하게 참조한다.cs - 현재 상태
123john = nil // "John Appleseed is being deinitialized"// john 도 Person 인스턴스를 강한 참조를 하지 않고// unit4A!.tenant 도 Person 인스턴스를 weak 참조로 참조하기 때문에 메모리는 해제 되고 deinit 함수 수행된다.cs - 현재 상태
- Person 인스턴스의 마지막 참조인 john 에 nil 이 할당되면서 메모리가 해제되기 때문에 Person 인스턴스를 약하게 참조하고 있던 tenant 프로퍼티에 nil 이 저장된다.
12unit4A = nil // "Apartment 4A is being deinitialized"cs - 현재 상태 : 두 개의 인스턴스 모두 해제된 상태.
- Unowned Reference
- What is Unowned Reference
- 'weak' 와 같은점 : 참조하는 인스턴스를 'strong reference' 로 묶지 않는다.
- 'weak' 와 다른점 :
- 나 말고 상대 인스턴스가, 즉 내가 묶고 있는 인스턴스가 나와 같거나 나보다 더 오래 생존할 것 일때 쓴다. ( weak 은 자기보다 생존주기가 짧은 곳에 썻었쥬?)
- nil 이 할당될 수 없음 ( 무조건 값이 들어감. )
- 여기서 헷갈리니까 정리.
- 주어에 대해 헷갈릴 수 있는데, 위에 예제에서 weak 은 tenant 에 붙였죠?
- tenant 가 가지고 있는 Person 인스턴스가 먼저 없어질 예정임.
- 그래서 Person 인스턴스를 가지고 있는 tenant에 weak 키워드를 붙인거임.
- 나( Apartment 인스턴스 ) 보다 내가 strong reference 로 묶고있는 너 ( Person 인스턴스 ) 가 먼저 해제할 생각에 있으므로 나보다 생존주기가 짧은 너에게 weak 붙일거야. 이 말이지.
- unowned 참조는 항상 값을 가져. 그러니까 ARC 는 거기에 nil 을 할당하지 않겠지. 그러니까 unowned 참조는 옵셔널 타입으로 정의되지 않겠지.
- 말로는 이해 안되. 코드로 이해하자
12345678910111213141516171819202122232425class Customer { // Customer 클래스let name: Stringvar card: CreditCard? // 옵셔널 타입 : 고객이 카드 사용하지 않을 수도 있으니까init(name: String) {self.name = name}deinit { print("\(name) is being deinitialized") }}class CreditCard {let number: UInt64unowned let customer: Customer// unownd : 강한 참조로 연결하지 않고 값이 무조건 있어야 함(not nil)// weak 은 var 로 해야했지만 unowned 는 let 가능이야^^. 안변하거든. 무조건 있어.// 논옵셔널 타입 : 신용카드에는 고객 정보가 필수로 있어야 함init(number: UInt64, customer: Customer) {self.number = numberself.customer = customer}deinit { print("Card #\(number) is being deinitialized") }}var john: Customer? //Customer 타입의 변수 옵셔널로 정의 , 아직 niljohn = Customer(name: "John Appleseed") // Customer 인스턴스 john 에 할당.john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)// john 이 가지고 있는 인스턴스로 card 프로퍼티 에 접근해서, CreditCard 인스턴스 할당.cs - 현재 상태
12345john = nil // 여기서 2 개의 과정이 일어남. 1: Customer 인스턴스 해제 2: CreditCard 인스턴스 해제// 이유는 아래 그림에서와 같이 john 에 nil 을 할당하면 Customer 인스턴스는 더 이상 자기를 강하게 참조해 주는 것이 없음// => 1. Customer 인스턴스 메모리 해제됨. "John Appleseed is being deinitialized" 출력// Customer 인스턴스 해제되면 CreditCard 인스턴스도 자기를 강하게 참조하는 것이 없음.// => 2. CreditCard 인스턴스 메모리 해제. "Card #1234567890123456 is being deinitialized" 출력cs - 현재 상태 :
- 클로저 에서 강한 참조 사이클 : Strong Reference Cycles for Closures
- 클래스 인스턴스의 프로퍼티에 클로저를 할당하고, 그 클로저의 실행 부분( body ) 에서 클래스 인스턴스를 참조할 때 ( capture ) '강한 참조 사이클' 문제가 발생합니다.
- 근본적인 원인은 클로저가 참조타입 이기 때문임.
- 아래 예제에서는 어떻게 클로저에서 강한 참조 사이클 문제가 발생하는지 설명합니다.
1234567891011121314151617181920212223242526272829class HTMLElement {let name: String // HTML 요소의 이름이래.let text: String? // 위의 요소에 의해 렌더링된 텍스트lazy var asHTML: () -> String = { // 클로저 시작.// lazy 키워드는 지연 저장 프로퍼티로 해당 클래스가 초기화 될 때 초기화 하지 않고 메모리를 절약해서,// 필요해서 호출할때 비로소 초기화 되서 메모리가 할당되게 하는 것.if let text = self.text { // 옵셔널 바인딩return "<\(self.name)>\(text)</\(self.name)>"// 클로저에서 HTMLElement 인스턴스의 name 참조.// 이 부분에서 '강한 참조 사이클' 문제가 발생.} else { // 텍스트 없으면return "<\(self.name) />"}}init(name: String, text: String? = nil) {self.name = nameself.text = text}deinit {print("\(name) is being deinitialized")}}var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")print(paragraph!.asHTML()) // "<p>hello, world</p>" 출력됨cs - 현재 상태
- 아래 그림에서 알 수 있는 것은 클로저도 '참조타입' 이기 때문에 하나의 인스턴스 처럼 생각해야 한다는 것입니다.
- 클로저가 HTML 인스턴스의 text 와 name 을 참조하고 있기 때문에 HTML 의 인스턴스를 강하게 참조하고 있다는 것을 알 수 있습니다.
- 해결방법
- 클로저를 정의하는 부분에서 capture list 를 정의함으로써 클로저와 클래스 인스턴스 사이의 '강한 참조 사이클' 문제를 해결한다.
12345678910111213141516171819202122232425262728class HTMLElement {let name: Stringlet text: String?lazy var asHTML: () -> String = { // 클로저 시작.[unowned self] in// capture list에 unowned 키워드를 사용해서 self 인 HTML 인스턴스와 강한참조 풀기.if let text = self.text {return "<\(self.name)>\(text)</\(self.name)>"// 클로저에서 HTMLElement 인스턴스의 name 참조.// 이 부분에서 '강한 참조 사이클' 문제가 발생.} else {return "<\(self.name) />"}}init(name: String, text: String? = nil) {self.name = nameself.text = text}deinit {print("\(name) is being deinitialized")}}var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")print(paragraph!.asHTML()) // "<p>hello, world</p>" 출력됨paragraph = nil // "p is being deinitialized" 출력됨 => 메모리 해제됨.cs - 현재 상태
- 클로저가 HTML 인스턴스를 강한참조로 묶고있지 않기 때문에 paragraph 에 nil 할당하면 ARC 가 HTML 인스턴스 메모리를 해제한다.
'스위프트: Swift > 스위프트: 언어자체: 문법' 카테고리의 다른 글
[스위프트 : 기초] 서브스크립트 : Subscript : 지름길 (0) 2018.10.08 Swift4 : 클로저: Closure: #표현방식: #왜필요해?: #효율적: #간결성: #생략 (0) 2018.09.20 Swift4 : 제네릭 : #Generics : #왜필요해? : #where키워드 : #제약사항걸기 (0) 2018.09.08 Swift4 : 프로토콜 2 : #델리게이트 패턴 : #델리게이션 (2 / 2) (0) 2018.09.07 Swift4 : 프로토콜 1 : #Protocol : #설계 : #요구사항 : #델리게이션 : #델리게이트 패턴 ( 1 / 2 ) (0) 2018.09.07 댓글