본문 바로가기

Programming/iOS

Swift 코드상에서 deinit호출 여부 체크하기

반응형

코드상에서 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를 통해 정확히 어디에서 참조가 유지되고 있는지 체크하면 됩니다.

 

예제 소스는 여기서 확인 가능합니다.

출처 : https://github.com/fastred/DeallocationChecker

반응형