관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] weak self 알고쓰기 (escaping closure, memory leak, weak self를 사용해도 crash가 나는 이유) 본문

iOS 기본 (swift)

[iOS - swift] weak self 알고쓰기 (escaping closure, memory leak, weak self를 사용해도 crash가 나는 이유)

jake-kim 2022. 3. 31. 22:45

사전 지식1) capture와 escaping의 개념

  • 공통점: 둘 다 closure에서 사용되는 개념
  • 차이점
    • capture: 클로저 내부에서 밖에 있는 scope의 instance를 참조하는 것
    • escaping: 클로저 외부에서 해당 클로저 자체를 참조하고 있는 것

사전 지식2) Escaping closure의 의미

  • `저장`되고, `지연`시킬 수 있는 기능 블록을 가지는 클로저가 바깥의 변수에 의해서 저장되는 경우 "Escaping closure"라고 정의
  • 인자로 전달받은 함수 중 함수의 리턴 이후에 실행될 수 있는 함수
    • 함수가 리턴되면 해당 scope은 사라지지만, closure는 함수의 scope을 escaping하여 함수 종료 후에 실행된다는 의미
// 함수의 return보다 completion이 늦게 실행되는 경우 (escaping closure)

func someFunction(completion: @escpaing () -> Void = {}) {
  self.someDelayProcess {
    completion()
  }
  print("someFunc!")
  return
}
  • 만약 아래처럼 단순히 return 전에 closure가 실행되는 경우에는 non-escaping
func someFunction(completion: () -> Void = {}) {
  completion()
  print("someFunc!")
  return
}

사전 지식3) guard let self = self 사용 시 주의할 점

  • 아래 그림에서 someView의 reference count는 몇개?
    • reference count는 현재 weak로만 잡혀있어서 0개인것 같지만, view2가 view1을 strong으로 잡고 있으므로 사실상 reference count가 한개 증가된 상태

// 코드로 알아보는 retain count

weak var view1: UIView?
var view2: UIView?

let sampleView = UIView()
print(CFGetRetainCount(sampleView)) // 2
self.view1 = sampleView
print(CFGetRetainCount(sampleView)) // 2
self.view2 = self.view1
print(CFGetRetainCount(sampleView)) // 3
  • 만약 메소드 내부에서 잡으면, 메소드가 종료되는 동시에 내부에서 strong으로 잡아도 reference count가 감소
    • 아래에서 알아볼 guard let self = self else { return }도 이처럼 캡쳐리스트로 weak self로 잡은 후 내부에서 strong으로 잡아도 결국 해당 클로저 종료 후 메모리 해제
// 메소드 안에서 strong으로 잡아도 메소드 scope이 종료되면 자동으로 RC가 줄어들음 

print(CFGetRetainCount(self.sampleView)) // 2
self.someFunc()
print(CFGetRetainCount(self.sampleView)) // 2


func someFunc() {
  weak var localWeakView = self.sampleView
  print(CFGetRetainCount(self.sampleView)) // 2
  let localStrongView = self.sampleView
  print(CFGetRetainCount(self.sampleView)) // 3
}
  • weak self를 사용한 클로저에서, gaurd let ss = self else { return } 적용 시 현재 self를 weak로 잡아놓았지만, guard let ss로 strong으로 잡아버리면 self의 reference count가 증가된 형태
    • 하지만 해당 reference count는 밖의 self를 weak로 잡은 다음 내부 scope에서 다시 strong으로 잡은 것이므로 되어 해당 scope이 끝나면 자동으로 reference count는 감소할 것
    • 만약 escaping closure에서 처음부터 weak self를 안쓰고 strong으로 잡아놓는다면 해당 클로저 scope에서 strong으로 잡게되어서 reference count가 감소되지 않을 것
import UIKit

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    
    print(CFGetRetainCount(self)) // 29
    self.process { [weak self] in
      print(CFGetRetainCount(self)) // 29
      guard let ss = self else { return }
      print(CFGetRetainCount(self)) // 30
    }
  }
  
  func process(completion: () -> Void = {}) {
    completion()
  }
}

weak self 알고 사용하기

  • escaping closure이 아닌 경우
    • self가 클로저 실행보다 늦게 dealloc되면 weak self안써도 무방
    • 만약 self가 클로저보다 먼저 dealloc되면, 클로저 내부에서 self를 참조하는 순간 크래시 발생하므로 weak self 사용할 것
  • escaping closure인 경우
    • weak self 사용할 것

https://medium.com/@almalehdev/you-dont-always-need-weak-self-a778bec505ef

* 참고

https://medium.com/@almalehdev/you-dont-always-need-weak-self-a778bec505ef

Comments