DeallocTests
DeallocTests 是用于 Swift iOS 应用的自动内存泄漏检测工具。DeallocTests 是一种特殊的单元测试,可以轻松添加到现有项目中。它们可以分别检查应用程序的孤立部分,以确保每个部分都正确管理内存。其基本原理很简单:DeallocTests 尝试实例化一个对象(ViewController、ViewModel、Manager 等),然后在短时间内尝试从内存中释放它。DeallocTests 检查对象是否正确调用了 deinit
——如果您没有保留循环且没有可能的内存泄漏,这将是这种情况。
当然,也有其他工具用于内存泄漏检测,例如 Instruments 和 Xcode 中的较新版本的 MemoryDebugger。这些工具体用于捕获特定的内存泄漏。DeallocTests 的独特之处在于,它们可以提供自动测试,并且可以在 CI 中运行它们。
意外创建内存泄漏很容易。内存泄漏有多种形式;不仅仅是因为忘记在闭包中添加 [weak self]
。这就是为什么防止它们发生非常重要。DeallocTests 可以帮助检测。
何时可以使用 DeallocTests?
DeallocTests 与使用 MVVM-C(带有 ViewCoordinators 的 MVVM)架构的应用程序配合良好。使用 Coordinator 有助于使 ViewController 独立且易于构建。DeallocTests 与 Swinject(依赖注入框架)配合使用效果很好。最好的是——DeallocTests 的使用不需要修改您应用程序的主要目标。
听起来太好了,有点不像真的 :-)? 等一下...
测试
-
我们可以关注视图控制器测试。在MVVM-C架构编写的应用程序中,通常有很多屏幕(视图控制器),它们与视图协调器一起分组。建议的方法是为每个视图协调器创建一个单独的释放测试场景。DeallocTests逐个向视图协调器展示视图控制器(展示方法不重要)。如果发现内存泄漏,测试将因错误而失败,这可以帮助您找到泄漏。-tested all of the coordinator's controllers,然后检查协调器本身是否存在内存泄漏。
-
测试“不可见”的对象。应用程序通常有很多封装业务逻辑的类:管理员、服务、模型、视图模型等。这些对象也可以进行释放检查。这里建议的方法是按简单性顺序创建测试场景。没有依赖的最简单类应该首先检查,然后是使用已经测试过的类作为其依赖的类,等等。在下面的图中,您应该首先测试
APIManager
,然后是WeatherService
,最后才是ForecastViewModel
Swinject
DeallocTests的主版本使用Swinject作为唯一的依赖。Swinject通常被认为是Swift中最领先的依赖注入框架,它几乎包含了每个STRV iOS应用程序。依赖注入的支持是一个巨大的好处,但如果需要,DeallocTests也可以在没有它的情况下工作。如果您在应用程序中不使用Swinject,请使用DeallocTestsSwinjectFree库
- 使用SPM安装:库
DeallocTestsSwinjectFree
- 使用Cocoapods安装,请在Podfile中添加
pod 'DeallocTests/SwinjectFree'
DeallocTests。用于自定义释放测试的易于使用的框架。
需求
- iOS 8.0+ / Mac OS X 10.10+ / tvOS 9.0+ / watchOS 2.0+
- Xcode 10.0+
安装
依赖管理器
CocoaPods
CocoaPods 是 Cocoa 项目的依赖管理器。您可以使用以下命令安装它
$ gem install cocoapods
要使用 CocoaPods 将 DeallocTests 集成到您的 Xcode 项目中,请在您的 Podfile
中指定它
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!
pod 'DeallocTests', '~> 1.0.0'
然后,运行以下命令
$ pod install
Swift 包管理器
要将 DeallocTests 作为 Swift 包管理器 包使用,只需在您的 Package.swift 文件中添加以下内容。
// swift-tools-version:4.2
import PackageDescription
let package = Package(
name: "HelloDeallocTests",
dependencies: [
.package(url: "https://github.com/DanielCech/DeallocTests.git", .upToNextMajor(from: "1.0.0"))
],
targets: [
.target(name: "HelloDeallocTests", dependencies: ["DeallocTests"])
]
)
手动
如果您不希望使用上述任何依赖管理器,可以将 DeallocTests 手动集成到您的项目中。
Git 子模块
- 打开终端,进入您的顶级项目目录,然后运行以下命令"如果"您的项目尚未初始化为 Git 仓库
$ git init
- 通过运行以下命令将 DeallocTests 添加为 git 子模块
$ git submodule add https://github.com/DanielCech/DeallocTests.git
$ git submodule update --init --recursive
-
打开新的
DeallocTests
文件夹,并将DeallocTests.xcodeproj
拖到您的应用程序 Xcode 项目的 Project Navigator 中。它应该嵌套在您的应用程序蓝色项目图标下方。它是在所有其他 Xcode 组之上还是之下并不重要。
-
在 Project Navigator 中选择
DeallocTests.xcodeproj
并检查其部署目标是否与您的应用程序目标匹配。 -
接下来,在 Project Navigator 中选择您的应用程序项目(蓝色项目图标),导航到目标配置窗口,然后在侧边栏的 "Targets" 下选择应用程序目标。
-
在那个窗口的标签栏中,打开 "General" 面板。
-
在 "Embedded Binaries" 部分下点击
+
按钮。 -
您将看到两个不同的
DeallocTests.xcodeproj
文件夹,每个文件夹中都包含两个不同的版本DeallocTests.framework
,它们都嵌套在Products
文件夹内。您可以从任意的
Products
文件夹中进行选择。 -
选择
DeallocTests.framework
。 -
这样就完成了!
DeallocTests.framework
将自动添加为目标依赖项、链接框架和链接框架,在复制文件构建阶段,这是在模拟器和设备上构建所需的全部内容。
嵌入的二进制文件
- 从 https://github.com/DanielCech/DeallocTests/releases 下载最新版本。
- 接下来,在 Project Navigator 中选择您的应用程序项目(蓝色项目图标),导航到目标配置窗口,然后在侧边栏的 "Targets" 下选择应用程序目标。
- 在那个窗口的标签栏中,打开 "General" 面板。
- 在 "Embedded Binaries" 部分下点击
+
按钮。 - 添加下载的
DeallocTests.framework
。 - 这样就完成了!
示例应用
SampleApps 文件夹包含一个示例工程,它演示了至少一些功能。该应用程序本身非常简单——导航堆栈中只有三个屏幕。所有屏幕都由 MainCoordinator
处理。
Podfile 为应用程序的测试目标添加了对 DeallocTests 的支持。
target 'DeallocTestsAppCocoapodsTests' do
pod 'DeallocTests', :path=>'../../'
end
文件 DeallocTestsConformances.swift
包含了所有测试类的 DeallocTestable 协议的实现。
import DeallocTests
@testable import DeallocTestsAppCocoapods
extension MainCoordinator: DeallocTestable {}
extension FirstViewController: DeallocTestable {}
extension SecondViewController: DeallocTestable {}
extension ThirdViewController: DeallocTestable {}
文件 MainCoordinatorDeallocTester.swift
也非常简单。它定义了 MainCoordinator 的测试场景。
var mainCoordinator: MainCoordinator? {
applyAssembliesToContainer()
return MainCoordinator()
}
这将初始化一个 Swinject 依赖容器并实例化主协调器。方法 applyAssembliesToContainer
定义在主目标中。主协调器测试的场景如下:(不要害怕,这是每个测试场景中常见的几乎模板代码。)
func test_mainCoordinatorDealloc() {
presentingController = showPresentingController()
deallocTests = [
DeallocTest(
objectCreation: { [weak self] _ in
return self?.mainCoordinator?.createFirstViewController()
}
),
DeallocTest(
objectCreation: { [weak self] _ in
return self?.mainCoordinator?.createSecondViewController()
}
),
DeallocTest(
objectCreation: { [weak self] _ in
return self?.mainCoordinator?.createThirdViewController()
}
),
DeallocTest(
objectCreation: { _ in
return MainCoordinator()
}
)
]
let expectation = self.expectation(description: "deallocTest test_mainCoordinatorDealloc")
performDeallocTest(
deallocTests: deallocTests,
expectation: expectation
)
waitForExpectations(timeout: 200, handler: nil)
}
变量 presentingController
是一个简单的空控制器,位于所有内容之上——它由 DeallocTests 框架处理。数组 deallocTests
包含四个项目。前三个为视图控制器,最后一个为视图协调器本身。闭包 objectCreation
从视图协调器初始化特定的视图控制器。《期望》是一个标准的 XCTestExpectation
,用于等待测试的结果。主要测试处理隐藏在 performDeallocTest
调用中。
示例应用故意在 SecondViewController.swift 中包含一个内存泄漏。这个类包含一个指向 self
的闭包。DeallocTests 控制台输出来如下
Checking:
Alloc FirstViewController
Dealloc FirstViewController
Checking:
Alloc SecondViewController
/Users/danielcech/Documents/[Development]/[Projects]/ios-research-dealloc-tests/Sources/Core/DeallocTester.swift:175: error: -[DeallocTestsAppCocoapodsTests.MainCoordinatorDeallocTester test_mainCoordinatorDealloc] : failed - Failed: dealloc test failed on classes: [DeallocTestsAppCocoapods.SecondViewController]
Checking:
Alloc ThirdViewController
Dealloc ThirdViewController
Checking:
Alloc MainCoordinator
Dealloc MainCoordinator
如果您将第一行取消注释并取消第二行的注释,您将看到保留周期消失,测试将成功。
someClosure = { number in self.view(number) }
// someClosure = { [weak self] number in self?.view(number) }
参与
欢迎提出问题和拉取请求!
作者
许可协议
DeallocTests遵循MIT协议发布。详情见LICENSE。