UIViewController变量在释放后仍然存在

问题描述:

我正在尝试编写测试来检查保留周期,但遇到了这种奇怪的行为。当将视图控制器设置为nil时,A UIViewController的属性不会取消分配。以此模拟对象为例: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设置为nil后,所有对象UIViewController仍然存在。

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

+0

但是,如果您查看两个测试,两者之间的唯一区别是我调用_ = vc.view,这导致我必须使用toNot()来使测试通过。这是不希望的。我创建了这个例子来展示最简单的对象如何导致这种持久性的发生。没有强大的引用周期发生,有些东西只是一个NSObject。它让我相信,当与视图交互时,控制器的属性会持续存在,但最终会在一段时间后解除分配。 – jsetting32

简短的回答:

添加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 
} 

顺便说一句,如果您正在寻找另外l确认视图控制器本身的释放时机,在deinit(以及您设置的controller = nil)中添加一条print语句,您会看到deinit的时间在做任何加载视图。

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

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