하루를살자

[Swift] 강한 순환 참조 본문

Swift

[Swift] 강한 순환 참조

Kai1996 2022. 8. 6. 22:11

1.0 클래스 프로퍼티간의 강한 순환 참조 

ARC 란?

위 글에서 정리한듯 ARC 는 Heap 영역에 instance 의 reference count 가 0 일때 자동으로 메모리를 해제해준다. 

강한 순환 참조 (strong reference cycle) 은 한쌍의 인스턴스의 가 서로를 참조하고 있어서 reference count 가 0 이 되지않기 때문에 deinit 이 안되는 상황을 말한다. 아래 예제 코드 를 살펴보자 

class Phone {
  let type: String
  var owner: Person?
  
  init(type: String){
    self.type = type
  }
  
  deinit {
    print("Phone Deinit")
  }
}

class Person {
  let name: String
  var phone: Phone?
  
  init(name: String){
    self.name = name
  }
  
  deinit {
    print("Person Deinit")
  }
}


var person1: Person? = Person(name: "Kai")
var phone1: Phone? = Phone(type: "iPhone")

person1!.phone = phone1
phone1!.owner = person1

변수 person1 은 Person 을 참조하고 있고, Person 의 프로퍼티인 phone 또한 Phone 의 메모리를 참조하고 있다. 

phone1 또한 같은 맥락으로 phone 과 person 을 참조 하게 된다. 

현재 Person 과 Phone 의 reference count 는 각각 2가 된다. 

여기서 변수 person1 과 phone1 을 nil 로 준다해도 메모리 상에서 person 의 phone 프로퍼티와 phone 의 person 프로퍼티가 서로를 참조하고 있기 때문에 reference count 가 0 이 되지 않고 그대로 메모리에 남아 버리게 된다. 

person1 = nil
phone1 = nil

강한 순환 참조 해결법 

클래스 타입 안의 프로퍼티 에서 강한 순환 참조가 발생할경우 해결할수 있는 방법은 Weak 혹은 Unowned reference 를 사용하는 방법이 있다. 이 두가지의 방법을 사용하면 강한 참조를 하지않고 참조 사이클을 만들수있다. 

 

Weak reference 

인스턴스 의 lifetime 이 비교적 적은곳에 사용할수있다. 

예를들면, 사람은 평생동안 휴대폰이 항상있는것이 아니기때문에 아래처럼 phone 이라는 프로퍼티 phone 에  weak 을 사용한다.

 

class Phone {
  let type: String
  var owner: Person?
  
  init(type: String){
    self.type = type
  }
  
  deinit {
    print("Phone Deinit")
  }
  
}

class Person {
  let name: String
  weak var phone: Phone?
  
  init(name: String){
    self.name = name
  }
  deinit {
    print("Person Deinit")
  }
}

var person1: Person? = Person(name: "Kai")
var phone: Phone? = Phone(type: "iPhone")
person1!.phone = phone
phone!.owner = person1
phone = nil


//Prints
//Phone Deinit

person1 이 참조하는 phonenil 이 되면서 RefereceCount 가 -1 이 된다. 

weak 으로 참조 하고 있었기 때문에 ReferenceCounter 가 0 이 되고 phone 은 메모리에서 deinit 이 된다.

 

Unowned 

Weak reference 와 마찬가지로 Unowned reference 또한 강한 참조를 하지 않는다. Weak 과는 다르게, unowned reference 는 인스턴스의 생명주기가 같거나 비슷하다면 사용한다. (swift 5.0 공식 문서에선 Unowned 에 optional 값을 주지못한다고 되어 있는데 xcode 에서 해보니까 옵셔널로 줘도 됐다. 이부분은 나중에 더 자세하게 알아봐야겠다.) https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html

 

Automatic Reference Counting — The Swift Programming Language (Swift 5.7)

Automatic Reference Counting Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. In most cases, this means that memory management “just works” in Swift, and you don’t need to think about memory management your

docs.swift.org

 

2.0 클로저로 인한 강한 순환 참조 

강한 순환참조는 클래스 안에 해당 인스턴스를 바디안에 캡처하는 클로저를 프로퍼티로 사용할경우 발생할수 있다.

예제 코드를 살펴보자
class Person {
  
  let name: String
  var description: String?
  
  lazy var introduce: () -> String = {
    return "안녕하세요. 저는 \(self.name) 입니다. "
  }
  
  init(name: String){
    self.name = name
  }
  deinit {
    print("Person Deinit")
  }
}

person 객체 안에 자신을 소개하는 introduce 라는 클로저가 lazy var 로 프로퍼티로 할당되어 있다.

이 클로저는 self.name 을 사용하는데, 이때 바디에서 self 즉, Person 객체 를 캡처 해서 사용하게 된다.

이때 사용 되는 "self" 는 강한참조를 하게 된다. 

 

var person1: Person? = Person(name: "Kai")
//1
print(person1?.introduce())
//2
person1 = nil

person1 의 introduce 클로저가 실행되면 referenceCounter 는 + 1 이 된다. 

이후 person1 을 nil 로 할당하여도 메모리에서 introduce 가 Person 을 참조 하고 있으므로 자동 해제가 안된다. 

 

강한 순환 참조 해결법 

클로저 와 상위 객체사이에서 강한 순환참조가 발생하는 경우 또한 위에서 설명한 weak, unowned reference 를 사용해서 문제를 해결할수 있다.

class Person {
  
  let name: String
  var description: String?
  
  lazy var introduce: () -> String = {
    [weak self] in 
    return "안녕하세요. 저는 \(self?.name) 입니다. "
  }
  
  init(name: String){
    self.name = name
  }
  deinit {
    print("Person Deinit")
  }
}
var person1: Person? = Person(name: "Kai")

print(person1?.introduce())
person1 = nil //Person Deinit"

Comments