Skip to content

Latest commit

 

History

History
283 lines (183 loc) · 7.41 KB

002. Automatic Reference Counting.md

File metadata and controls

283 lines (183 loc) · 7.41 KB

Automatic Reference Counting

2021년 7월 2주차

목차

  1. 1. ARC란
  2. 2. JAVA의 Garbage Collection과 차이점
  3. 3. Reference 종류
  4. 4. Closure의 강한 참조 순환 문제

1. ARC란

1. 정의

: 자동 참조 카운팅, 자동으로 메모리를 관리해주는 방식

2. 작동 방식

  • 객체의 lifetime을 계산하여 자동으로 컴파일 타임retain/release 같은 메모리 관리 코드를 삽입해준다
  • retain/release는 컴파일 타임에 삽입되지만, 런타임에 실행된다
  • 클래스의 인스턴스를 만들 때마다 인스턴스 정보를 담는 메모리를 할당한다
  • 인스턴스에 대한 참조 개수를 세어 해당 인스턴스에 대한 참조를 갖고 있는지 추적한다
  • 최소 하나라도 그 인스턴스에 대한 참조가 있는 경우, 인스턴스를 메모리에서 해지하지 않는다

2. JAVA의 Garbage Collection과 차이점

메모리 관리 기법 ARC GC
참조 카운팅 시점 컴파일 시 프로그램 동작 중
장점 - 높은 성능
- 메모리를 적게 씀
(실행 중에 메모리 관리를 안 하기 때문)
- 예측하기 좋음
- 대부분의 상황에서 인스턴스를 해제함
- 규칙을 신경 쓸 필요가 없어 사용하기 쉬움
단점 - 특정 상황에서 메모리가 영원히 해제되지 않을 수 있음
( ex. 순환참조 )
- CPU/메모리 요구를 정확하게 예측할 수 없음
- 대체로 ARC보다 성능이 낮음

3. Reference 종류

1. Strong Reference

  • 별도의 식별자를 지정하지 않는 경우 기본적으로 강한참조를 한다
  • 강한 참조 시, 참조 카운트가 1 증가
  • 강한 참조 순환 문제로 메모리 leak을 야기한다
    • 수동으로 해결 가능
    • 약한 참조와 미소유 참조로 해결 가능

강한 참조 순환 문제

class Person {
	let name: String
  var room: Room?
  
  init(name: String) {
		self.name = name
  }
  deinit {
		print("\(name) is being deinitialized")
  }
}  

class Room {
	let number: String
  var host: Person?
  
	init (number: String) {
    self.number =number
  }
  deinit {
		print("Room \(number) is being deinitialized")
  }
}


var lia: Person? = Person(name:"Lia")		// Person 인스턴스의 참조 횟수: 1
var room: Room? = Room(number: "316")	// Room 인스턴스의 참조 횟수: 1

room?.host = lia 		// Person 인스턴스의 참조 횟수: 2
lia?.room = room		// Room 인스턴스의 참조 횟수: 2

lia = nil		// Person 인스턴스의 참조 횟수: 1
room = nil		// Room 인스턴스의 참조 횟수: 1

// Person 인스턴스를 참조할 방법 상실 - 메모리에 잔존 
// Room 인스턴스를 참조할 방법 상실 - 메모리에 잔존 

수동으로 해결 가능

var lia: Person? = Person(name: "Lia")	// Person 인스턴스의 참조 횟수: 1
var room: Room? = Room(number: "316")		// Room 인스턴스의 참조 횟수: 1

room?.host = lia	// Person 인스턴스의 참조 횟수: 2
lia?.room = room	// Room 인스턴스의 참조 횟수: 2

lia?.room = nil		// Room 인스턴스의 참조 횟수: 1
lia = nil		// Person 인스턴스의 참조 횟수: 1

room?.host = nil		// Person 인스턴스의 참조 횟수: 0
// Lia is being deinitialized

room = nil		// Room 인스턴스의 참조 횟수: 0
// Room 316 is being deinitialized

2. Weak Reference

  • weak 키워드 사용
  • 참조하는 인스턴스의 참조 횟수를 증가시키지 않음
  • 무조건 optional 변수여야 함 (nil이 할당될 수 있어야하기 때문)
class Room{
  let number: String
  weak var host: Person?
  
  init(number: String) {
    self.number = number
  }  
  deinit {
    print("Room \(number) is being deinitailized")
  }
}

var lia: Person? = Person(name: "Lia")	// Person 인스턴스의 참조 횟수: 1
var room: Room? = Room(number: "316")		// Room 인스턴스의 참조 횟수: 1

room?.host = lia		// Person 인스턴스의 참조 횟수: 1
lia?.room = room 		// Room 인스턴스의 참조 횟수: 2

lia = nil 	// Person 인스턴스의 참조 횟수: 0, Room 인스턴스의 참조 횟수: 1
// Lia is being deinitialized
print(room?.host)		//nil

room = nil	// Room 인스턴스의 참조 횟수: 0
// Room 316 is being deinitialized

3. Unowned Reference

  • unowned 키워드 사용
  • 참조하는 인스턴스의 참조 횟수를 증가시키지 않음
  • 자신이 참조하는 인스턴스가 메모리에서 해제되어도 스스로 nil을 할당해주지 않음
  • optional이 아니어도 됨
  • 옵셔널 또는 암시적 추출 옵셔널 프로퍼티로 쓸 수도 있긴 함
  • 메모리에서 해제된 인스턴스에 접근하려고 하면 강제 종료됨
  • 그러므로 웬만하면 지양하자
class Person{
  let name: String
  var card: CreditCard?
  // 카드를 소지할 수도, 소지하지 않을 수도 있기 때문에 옵셔널로 정의
  // 카드를 한 번 가지면 참조를 잃지 않아야 하므로 강한참조를 해야함

  init(name: String) {
    self.name = name
  }
  deinit{ print("\(name) is being deinitialized")}
}

class CreditCard {
  let number: UInt
  unowned let owner: Person		// 카드는 소유자가 분명히 존재해야함
  
  init(number: UInt, owner: Person) {
    self.number = number
    self.owner = owner
  }
  deinit { print("Card #\(number) is being deinitialized")}
}

var lia: Person? = Person(name: "Lia")	// Person 인스턴스의 참조 횟수: 1

if let person: Person = lia {
  // CreditCard 인스턴스의 참조 횟수: 1
  person.card = CreditCard(number: 1234, owner: person)
  // Person 인스턴스의 참조 횟수: 1
}

lia = nil //Person 인스턴스의 참조 횟수: 0
// CreditCard 인스턴스의 참조 횟수: 0

// Lia is being deinitialized
// Card #1234 is being deinitialized

4. Closure의 강한 참조 순환 문제

  • 클로저는 참조 타입
  • 클로저의 값 획득 특성 때문에 발생
  • capture list 에 reference type 을 명시하여 해결 가능

문제 상황

class Person {
	let name : String
	
	lazy var introduce: () -> String = {
		retrun "My name is \(self.name)"
	}
	
	init(name: String) {
		self.name = name
	}
	deinit {
		print("\(name) is being deinitialized")
	}
}

var lia: Person? = Person(name: "Lia")
print(lia?.introduce) // My name is Lia

lia = nil

Capture List를 통한 해결

class Person {
	let name : String
	
	lazy var introduce: () -> String = { [weak self] in
		return "My name is \(self?.name)"
	}
	
	init(name: String) {
		self.name = name
	}
	deinit {
		print("\(name) is being deinitialized")
	}
}

var lia: Person? = Person(name: "Lia")
print(lia?.introduce) // My name is Lia

lia = nil // Lia is being deinitialized

출처