【问题标题】:UIViewController variables persist after deallocationUIViewController 变量在释放后仍然存在
【发布时间】:2017-08-25 22:11:48
【问题描述】:

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

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。我的问题是为什么?为什么当我引用UIViewControllerview 属性时,即使将UIViewController 设置为nilUIViewController 拥有的所有对象仍然存在。

FWIW,我正在使用 QuickNimble 进行测试,以及 Swift 3.1

【问题讨论】:

  • 但是如果你查看这两个测试,两者之间唯一的区别就是我调用了_ = vc.view,这导致我不得不使用toNot()来使测试通过。这是不希望的。我创建了这个例子来展示即使是最简单的对象也是如何导致这种持久性发生的。没有发生强引用循环,只是一个 NSObject。这让我相信,当与视图交互时,它的控制器属性会持续存在,但最终会在一段时间后被释放。

标签: ios swift memory-leaks ios10


【解决方案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
}

顺便说一句,如果您想进一步确认视图控制器本身的释放时间,请在 deinit(以及您设置 controller = nil 的位置)中添加 print 语句,然后您将看到deinit 的时间在执行任何加载视图的操作时发生变化。

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

至少我们可以用autoreleasepool明确控制自动释放池生命周期时间。

【讨论】:

    猜你喜欢
    • 2011-06-16
    • 1970-01-01
    • 2019-10-22
    • 2016-12-09
    • 2011-08-21
    • 2019-06-30
    • 1970-01-01
    • 2021-06-15
    • 1970-01-01
    相关资源
    最近更新 更多