1 minute read

전까지는 클래스끼리 변수에서의 강한 참조 순환을 봤는데 변수 뿐만아니라 클로저와 관계되어서 발생할 수도 있습니다. 그래서 이번에는 마지막으로 클로저의 강한 참조 순환에 대해서 알아보겠습니다

클로저는 completion blocks, callbacks 고차함수 등등 많은 고셍서 사용되는 것을 볼 수 있습니다.

var sayHello = { (name: String) -> String in
  return "Hello \(name)"
}

이렇게 말이죠.

그리고 이스케이핑 클로저에서는 self를 명시적으로 언급해야하는데 그때 강한 참조 순환이 일어날 수 있습니다.

클로저에서의 강한 순환 참조

그리고 클로저에서는 selff를 캡처하기 때문에 강한 참조 순환이 발생할 수도 있습니다. 그럴경우 클로저 캡쳐 리스트를 사용하여 강한 참조 순환을 해결할 수 있습니다.

class HTMLElemnet {
	let name: String
  let text: String
  lazy var asHTML: () -> String = { //클로저 사용
    if let text = self.text { //self 캡쳐!
      return "\(self.name)text"
    }else {
      return "\(self.name)"
    }
  }
  
  init(name: String, text: String? = nil) {
    self.name = name
    self.text = text
  }
  
  deinit {
    print("deinit")
  }
}

여기 HTMLElement 클래스가 있습니다.

var paragraph: HTMLElement? = HTMLElement(name: "p", text: " hello world")
print(paragraph!.asHTML())

paragraph가 옵셔널로 선언되어서 nil을 할당할 수 있지만 인스턴스와 클로저 간에 강한 참조 순환이 일어납니다 그래서 nil을 할당하더라도 해당 인스턴스는 해제되지 않습니다.

클로저에서의 강한 순환 참조 해결

클로저에서 캡쳐 참조에 약한 참조 혹은 미소유 참조를 지정하면 해결할 수 있습니다. 이때 약한 참조와 미소유 참조인가를 정할 땐 코드의 상호관계에 달려있습니다.

  1. 캡쳐리스트 정의

    lazy var someClosure: (Int, String) -> String = {
      [unowned self, weak delegate = self.delegate!] in
    }
    
  2. 약한 참조와 미소유 참조

    • 약한 참조는 참조가 먼저 해제되는 경우에 사용합니다
    • 미소유 참조는 같은 시점이나 나중 시점에 해제되는 경우입니다. 하지만 캡쳐리스트가 절대 nil이 될 수 없다면 그것은 반드시 약한 참조가 아닌 미소유 참조로 캡쳐되어야합니다.
    class HTMLElemnet {
    	let name: String
      let text: String
      lazy var asHTML: () -> String = {
            [unowned self] in
            if let text = self.text {
                return "<\(self.name)>\(text)</\(self.name)>"
            } else {
                return "<\(self.name) />"
            }
        }
      init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
      }
      deinit {
        print("deinit")
      }
    }
       
    var paragraph: HTMLElement? = HTMLElement(name: "p", text: " hello world")
    print(paragraph!.asHTML())
       
    paragraph = nil //메모리에서 해제됨!
    

    아까의 코드와 달라진 점은 클로저에 캡쳐리스트로 미소유 참조를 추가하였습니다. 앞서와는 다르게 클로저에서 HTMLElement 인스턴스를 미소유 참조로 참조하고 있습니다. 결국 강한 참조 순환이 되지 않으므로 마지막 줄에서 nil을 할당해주어도 메모리에서 해제될 수 있습니다.

Tags:

Categories:

Updated: