首页 > 解决方案 > 关闭视图控制器时在应用程序中调用 deinit 但在单元测试中未调用

问题描述

我正在尝试对视图控制器的内存进行单元测试,以查看它们在被解雇时是否正确取消初始化。

class SettingsViewControllerTests: XCTestCase {

    var controller: SettingsViewController!

    override func setUp() {
        super.setUp()

        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        controller = storyboard.instantiateViewController(withIdentifier: "SettingsViewController")
            as? SettingsViewController

        //load view hierarchy
        _ = controller.view
    }

    func testLogout() {
        let sideMenu = MockSideMenuViewController()
        var navController: UINavigationController? = UINavigationController(rootViewController: sideMenu)
        sideMenu.show(navController!, sender: nil)
        navController?.pushViewController(controller, animated: true)
        expect(navController).toNot(beNil())
        controller.dismiss(animated: false, completion: nil)

        expect(navController).toEventually(beNil(), timeout: 3) // fails
        expect(self.controller).toEventually(beNil(), timeout: 3) // fails
    }

在应用程序中,我使用 segue 来展示我的导航控制器 + SettingsViewController。它有一个调用dismiss自身的方法,但是当我检查我的控制器实例时,它们仍然存在。在我的视图控制器中,我设置了一个打印语句来检查是否正在调用 deinit,并且当我通过应用程序上的步骤时,它确实被调用(两个控制器最终都转到nil)。然而,单元测试并没有做同样的事情。我错过了什么?

标签: iosswiftunit-testinguikit

解决方案


controller是一个强烈持有的财产,所以你的测试是保留它。现在它只会在后续调用setUp(). 如果您想controller.deinit专门测试,请controller = nil在测试中进行。

你可能会做这样的事情:

var controller: SettingsViewController!  // <--- this is a strong ref

func testLogout() {
        let sideMenu = MockSideMenuViewController()

        // navController is a strong ref, held until the end of the scope; don't expect it to be nil
        var navController: UINavigationController? = UINavigationController(rootViewController: sideMenu)
        sideMenu.show(navController!, sender: nil)
        navController?.pushViewController(controller, animated: true)

        // hold a weak ref to your controller and then nil out its reference
        weak var weakController = controller

        // remove the strong reference
        controller = nil

        // popping will release the last reference 
        navController?.popViewController(animated: false)

        expect(weakController).to(beNil(),) // succeeds
    }

一些注意事项:

  • navController是范围级别的变量。在函数结束之前它不会是 nil,所以没有理由测试或期望它。在你创建它之后它也肯定不会是 nil。

  • UIViewController.dismiss(...)用于解除模式。您的控制器是导航堆栈的一部分。解雇不会做你所期望的。

感觉就像您期望var navController: UINavigationController?自己变弱一样,但事实并非如此。 weakvars 和 properties 应该是Optional,但作为 Optional 并不意味着weak。请参阅弱引用


推荐阅读