iOS Swift项目的内存泄漏集成测试

ARC在iOS编程世界中有很大帮助,但是像Java中的GC一样,它不能解决保留周期的问题。 GC在运行时工作,可以检测到保留周期,而无需引用该过程的“根”对象,然后销毁它。 但是ARC正在编译时间,因此它不能这样做。

即使您的泄漏对象很小,并且不会占用太多内存,但是如果它们保留在内存中并可能对应用程序造成奇怪的行为,您仍然需要小心。

在本文中,我将解释一种简单的方法来为iOS swift项目中的内存泄漏添加集成测试。 我还将讲述我面临的问题
添加内存泄漏集成测试以及解决方法。

请在此处下载启动项目:https://github.com/bigbangvn/MemoryLeakIntegationCheck/blob/master/LeakIntegrationCheck_StartProject.zip

尝试运行该应用程序,然后运行测试“ LeakIntegrationCheckTests”。 它现在将失败,但是我们将要修复。

这个例子很简单。 我们只有一个登录屏幕和一个主屏幕。 我们想测试一下是否存在内存泄漏,在用户登录到主屏幕后,执行一些请求并注销。

首先,对于纯swift对象,没有任何麻烦,我们需要添加代码来跟踪实例数。 避免测试代码影响生产。 我们将使用一个标志。
转到项目->构建设置->活动编译条件,然后将ENABLE_LEAK_CHECK添加到要测试的目标,您可以仅使用“调试”目标。

转到类HomeManager,在该类的末尾,添加以下代码以跟踪实例数:

  #if ENABLE_LEAK_CHECKinit(){type(of:self).instanceCount + = 1print(“ \(String(describing:self))\(#function)”)}静态var instanceCount = 0deinit {type(of:self).instanceCount -= 1print(“ \(字符串(描述:自我))\(#功能)”)}#endif 

然后对HomeViewController,MyService,MyEntity执行相同的操作。 ( 这种实例计数跟踪的方法目前是相当手动的。我们可以通过添加一个类来跟踪实例计数,甚至跟踪与对象实例相关联的调用堆栈,来进行一些改进

现在转到您的测试“ LeakIntegrationCheckTests.swift”,并查看该函数:

  func testLeakAfterLogout(){ 
}

在第57行的XCTAssert中注释掉第24行的注释。再次运行测试,它将通过。 您可能会问:为什么只有将XCTAssert放在tearDown()中才能通过测试?
让我解释!

当我们调用“ AppDelegate.shared.switchToLoginScreen()”以注销时,我们更改了窗口rootViewController。 尽管Window不再保留对旧ViewController(HomeViewController)的引用,但UIKit仍需要安排一些与旧ViewController有关的任务。 而且这些任务只能在我们的测试方法:testLeakAfterLogout()完成并将控制返回到主运行循环之后才能继续。

这就是为什么我们需要将泄漏检查放在tearDown()函数中的原因。 另外,我们需要在testLeakAfterLogout()末尾调用delayAndFlushTasks()来强制主线程执行所有待处理的任务,因此,在我们的测试函数返回之后,主运行循环可以使用旧的RootViewController继续执行计划的任务,并停止保留它。

在HomeViewController的deinit()上放置一个断点,您将看到deinit()由应用程序主循环触发。 这意味着HomeViewController在更改Window RootViewController之后不会释放,它可能在应用程序主运行循环的下一次迭代中释放。

现在,让我们对测试做一件事。 转到HomeViewController.swift并取消注释第29–33行。 再次运行测试,您将看到测试失败。 查看HomeManager中的function requestHomeInfo(),您将看到有一个asyncAfter块,该块保存对self的引用。
要修复测试,请再次进行测试,然后在函数testLeakAfterLogout()的第52行中,将值0更改为5.0(秒)。 运行测试,它应该现在通过。

我想你明白了。 在实际的应用程序中,有时您可能想要避免释放对象,直到完成某些操作为止。

希望这个小例子可以帮助您改善项目!