LifetimeTracker
条形样式 | 圆形样式 |
LifetimeTracker 可以在您开发应用的过程中即时揭示保留周期/内存问题,让您更容易找到它们。
Instruments 和 Memory Graph Debugger 非常好,但很多时候开发者在关闭功能实现时忘记检查问题。
如果您偶尔使用这些工具,它们揭示的大多数问题都需要您调查原因,这个过程会花费您大量时间。
其他工具如 FBRetainCycleDetector 依赖于 objc 运行时魔法来查找问题,但这意味着它们实际上不能用于纯 Swift 类。这个小工具仅仅专注于跟踪对象的寿命,这意味着它可以在 Objective-C 和 Swift 代码库中使用,并且不依赖于任何复杂或自动的魔法行为。
安装
CocoaPods
将 pod 'LifetimeTracker'
添加到您的 Podfile 中。
Carthage
将 github "krzysztofzablocki/LifetimeTracker"
添加到您的 Cartfile 中。
集成
要将视觉通知集成,请在 AppDelegate(didFinishLaunchingWithOptions:)
的开始处添加以下行。
Swift
#if DEBUG
LifetimeTracker.setup(onUpdate: LifetimeTrackerDashboardIntegration(visibility: .alwaysVisible, style: .bar).refreshUI)
#endif
Objective-C
LifetimeTrackerDashboardIntegration *dashboardIntegration = [LifetimeTrackerDashboardIntegration new];
[dashboardIntegration setVisibleWhenIssueDetected];
[dashboardIntegration useBarStyle];
[LifetimeTracker setupOnUpdate:^(NSDictionary<NSString *,EntriesGroup *> * groups) {
[dashboardIntegration refreshUIWithTrackedGroups: groups];
}];
您可以通过设置参数来控制仪表板的可见性: alwaysVisible
、alwaysHidden
或 visibleWithIssuesDetected
。
提供了两种样式。一种是在屏幕上显示详细问题列表的覆盖条视图,另一种是以圆形视图显示问题数量,并作为模态视图控制器打开详细列表。
跟踪关键角色
通常您希望使用 LifetimeTracker 来跟踪应用程序中的关键角色,如 ViewModels、/ Controllers 等。当活着的项数超过 maxCount
时,追踪器会通知您。
Swift
您需要实现 LifetimeTrackable
协议并在 init 函数的末尾调用 trackLifetime()
。
class SectionFrontViewController: UIViewController, LifetimeTrackable {
class var lifetimeConfiguration: LifetimeConfiguration {
return LifetimeConfiguration(maxCount: 1, groupName: "VC")
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
/// ...
trackLifetime()
}
}
Objective-C
您需要实现 LifetimeTrackable
协议并在 init 函数的末尾调用 [self trackLifetime]
。
@import LifetimeTracker;
@interface SectionFrontViewController() <LifetimeTrackable>
@implementation SectionFrontViewController
+(LifetimeConfiguration *)lifetimeConfiguration
{
return [[LifetimeConfiguration alloc] initWithMaxCount:1 groupName:@"VC"];
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
/// …
[self trackLifetime];
}
return self;
}
@end
与 Danger 集成
如果您使用 Danger,可以使用它添加到每个 PR 的复选框,以确保人们已验证没有创建保留循环,而且当有人忘记调用 trackLifetime()
函数时通知您。
#
# ** FILE CHECKS **
# Checks for certain rules and warns if needed.
# Some rules can be disabled by using // danger:disable rule_name
#
# Rules:
# - Check if the modified file is a View and doesn't implement LifetimeTrackable (lifetime_tracking)
# Sometimes an added file is also counted as modified. We want the files to be checked only once.
files_to_check = (git.modified_files + git.added_files).uniq
(files_to_check - %w(Dangerfile)).each do |file|
next unless File.file?(file)
# Only check inside swift files
next unless File.extname(file).include?(".swift")
# Will be used to check if we're inside a comment block.
is_comment_block = false
# Collects all disabled rules for this file.
disabled_rules = []
filelines = File.readlines(file)
filelines.each_with_index do |line, index|
if is_comment_block
if line.include?("*/")
is_comment_block = false
end
elsif line.include?("/*")
is_comment_block = true
elsif line.include?("danger:disable")
rule_to_disable = line.split.last
disabled_rules.push(rule_to_disable)
else
# Start our custom line checks
# e.g. you could do something like check for methods that only call the super class' method
#if line.include?("override") and line.include?("func") and filelines[index+1].include?("super") and filelines[index+2].include?("}")
# warn("Override methods which only call super can be removed", file: file, line: index+3)
#end
end
end
# Only continue checks for Lifetime Trackable types
next unless (File.basename(file).include?("ViewModel") or File.basename(file).include?("ViewController") or File.basename(file).include?("View.swift")) and !File.basename(file).include?("Node") and !File.basename(file).include?("Tests") and !File.basename(file).include?("FlowCoordinator")
if disabled_rules.include?("lifetime_tracking") == false
if File.readlines(file).grep(/LifetimeTrackable/).any?
fail("You forgot to call trackLifetime() from your initializers in " + File.basename(file, ".*") + " (lifetime_tracking)") unless File.readlines(file).grep(/trackLifetime()/).any?
else
warn("Please add support for LifetimeTrackable to " + File.basename(file, ".*") + " . (lifetime_tracking)")
end
markdown("- [ ] I've verified that showing and hiding " + File.basename(file, ".*") + " doesn't surface any [LifetimeTracker](https://github.com/krzysztofzablocki/LifetimeTracker) issues")
end
end
分组追踪对象
您可以将追踪对象分组。默认情况下,组的 maxCount
会根据每个成员的 maxCount
计算。但是,您可以通过提供单独的值来覆盖它,使用 groupMaxCount
为组。
当您有一组子类可以出现 x 次,但在总数上小于所有子类的和时,您可能想这么做。
// DetailPage: UIViewController
// VideoDetailPage: DetailItem
LifetimeConfiguration(maxCount: 3, groupName: "Detail Page")
// ImageDetailPage: DetailItem
LifetimeConfiguration(maxCount: 3, groupName: "Detail Page")
=> Group warning if 7 DetailPage objects are alive
// VideoDetailPage: DetailItem
LifetimeConfiguration(maxCount: 3, groupName: "Detail Page", groupMaxCount: 3)
// ImageDetailPage: DetailItem
LifetimeConfiguration(maxCount: 3, groupName: "Detail Page", groupMaxCount: 3)
=> Group warning if 4 DetailPage object are alive
编写内存泄漏的集成测试
您可以使用无障碍标识符 LifetimeTracker.summaryLabel
访问摘要标签,这允许您编写集成测试来最终查找是否发现了任何问题。
许可
LifetimeTracker 在 MIT 许可下提供。有关更多信息,请参阅 LICENSE。
致谢
我使用 SwiftPlate 生成了与 CocoaPods 和 Carthage 兼容的 xcodeproj。