解除分配后,UIViewController变量仍然存在

时间:2017-08-25 22:11:48

标签: ios swift memory-leaks ios10

我正在尝试编写测试以检查保留周期但遇到了这种奇怪的行为。将视图控制器设置为UIViewController时,不会取消分配nil的属性。以此模拟对象为例:

class BasicViewController: UIViewController {
   var someObject = NSObject()
   .....
}

它拥有的只是一个变量。你会认为在调用basicViewController = nil时会导致someObject为零,但不是。

it("releases someObject") {
  var controller: MockController? = MockController()
  weak var something = controller?.something
  expect(controller).toNot(beNil())
  controller = nil
  expect(controller).to(beNil())
  expect(something).to(beNil())
}

it("doesn't release someObject") {
  var controller: MockController? = MockController()
  weak var something = controller?.something
  expect(controller).toNot(beNil())
  _ = controller?.view
  controller = nil
  expect(controller).to(beNil())
  expect(something).toNot(beNil())
}

调用vc.view时,会调用loadView以及UIViewController的生命周期函数 - viewDidLoadviewDidAppearviewWillAppear。我的问题是为什么?为什么当我引用UIViewController的{​​{1}}属性时,即使将view设置为UIViewControllerUIViewController拥有的所有对象仍然存在。

FWIW,我正在使用nilQuick进行测试,以及Nimble

1 个答案:

答案 0 :(得分:1)

简短回答:

添加一个autoreleasepool,它将精确地指示何时从池中排出对象,并且它可以按照您的预期运行。

答案很长:

我遇到了你描述的相同行为。但问题不在于视图控制器的属性。它是视图控制器本身。

在您的示例中,您将controller设置为nil并使用现在为nil的事实来推断控制器是否已被解除分配。但这仅测试对视图控制器的特定引用是否为nil,但视图控制器本身可能尚未被释放。但您可以将weak var测试与视图控制器本身一起使用。考虑一下这个视图控制器:

class BasicViewController: UIViewController {
    // this is intentionally blank
}

我可以编写测试,其中视图控制器显示您描述的行为,加载视图后XCTAssertNil测试失败的地方:

class MyApp2Tests: XCTestCase {

    func testWithoutView() {
        var controller: BasicViewController? = BasicViewController()
        weak var weakController = controller
        XCTAssertNotNil(weakController)
        controller = nil
        XCTAssertNil(weakController)          // this succeeds
    }

    func testWithView() {
        var controller: BasicViewController? = BasicViewController()
        weak var weakController = controller
        XCTAssertNotNil(weakController)
        controller?.loadViewIfNeeded()
        controller = nil
        XCTAssertNil(weakController)          // this fails
    }

}

但是当我添加autoreleasepool来明确控制池何时耗尽时,它按预期工作:

func testWithViewAndAutoreleasePool() {
    weak var weakController: BasicViewController?
    autoreleasepool {
        var controller: BasicViewController? = BasicViewController()
        weakController = controller
        XCTAssertNotNil(weakController)
        controller?.loadViewIfNeeded()
        controller = nil
    }
    XCTAssertNil(weakController)          // this succeeds
}

BTW,如果您正在寻找关于视图控制器释放时间的其他确认,请在print中添加deinit语句(以及您设置{{1}的位置并且你会看到controller = nil的时间在做任何加载视图的事情时发生变化。

我无法解释这种行为。为什么要对deinit做一些事情会影响视图控制器的生命周期?顺便说一下,我也用视觉控制器的属性执行了上述测试,就像在你的问题中一样,我看到完全相同的行为(但恕我直言,这并不奇怪,因为它只是因为视图控制器本身尚未被释放)。

至少我们可以使用view显式控制自动释放池的生命周期时间。