반응형
코드상에서 UIViewController 벗어날 때 retain cycle 해지가 되지 않아
메모리에 상주해 있는 경우가 있습니다.
처음 UIViewController를 만들고 작업 했을 때는 생기지 않았지만, 그 이후에 추가기능을 넣고 빼는 과정에서
특정 객체 참조가 해지 되지 않거나,
self 참조 등으로 retain cycle이 다 해지가 되지 않는 경우
이 UIViewController는 계속 메모리에 남아 있는데요
Instruments가 아닌 코드상으로 체크 할 수 있는 방법입니다.
UIViewController있는 아래 두 property를 검사 할텐데요
- isBeingDismissed: UIViewController가 dismiss될 때, 해당 값은 true입니다.
- isMovingFromParent: 부모 UIViewController에서 이 UIViewController가 제거 될 때 true가 됩니다.
이 두 Property중 하나만 true이여도 화면이 제거 된다는 것을 의미 합니다.
이를 토대로 화면을 빠져나갈 때 제대로 deinit 상태로 넘어가는지 체크 하면 됩니다.
코드는 아래와 같습니다.
dch_checkDeallocaiton()에서 위의 Property값을 체크하는데요,
화면이 빠져나간 뒤 약 2초뒤에 검사를 진행합니다.(deinit이 2초는 넘지 않을 거란 가정 하에)
public enum DeallocShowType {
case alert
case assert
case log
}
extension UIViewController {
public func dch_checkDeallocation(showType: DeallocShowType = .alert, afterDelay delay: TimeInterval = 2.0) {
let rootParentViewController = dch_rootParentViewController
if isMovingFromParent || rootParentViewController.isBeingDismissed {
let t = type(of: self)
let disappearanceSource: String = isMovingFromParent ? "removed from its parent" : "dismissed"
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
let message = "\(t) not deallocated after being \(disappearanceSource)"
switch showType {
case .alert:
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
UIApplication.topViewController()?.present(alert, animated: true, completion: nil)
case .assert:
assert(self == nil, message)
case .log:
NSLog("%@", message)
}
}
}
}
private var dch_rootParentViewController: UIViewController {
var root = self
while let parent = root.parent {
root = parent
}
return root
}
}
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
} else if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
return topViewController(base: selected)
} else if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
검사는 UIViewController의 viewDidDisappear()를 통해서 진행합니다.
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
dch_checkDeallocation()
}
deinit이 호출되지 않은 경우, 아래와 같이 에러를 확인할 수 있습니다.
에러가 발생한 경우
Instruments를 통해 정확히 어디에서 참조가 유지되고 있는지 체크하면 됩니다.
예제 소스는 여기서 확인 가능합니다.
반응형
'Programming > iOS' 카테고리의 다른 글
ObjC Framework import 시 'Failed to build module'로 인해 import할 수 없는 경우 (0) | 2021.03.09 |
---|---|
Swift 메뉴 화면 제작 (0) | 2020.03.10 |
iOS command build script (0) | 2019.12.10 |
Observer Pattern in Swift (0) | 2019.12.10 |
iOS 서버별 자동배포 환경 구축 방법 (0) | 2019.12.10 |