![]() |
![]() |
|
---|---|---|
条形样式 | 圆形样式 |
LifetimeTracker 可以在您开发应用程序时立即发现 retain cycle / 内存问题,并立即向您展示这些问题,以便您可以更轻松地找到它们。
Instruments 和 Memory Graph Debugger 非常棒,但是很多时候,开发人员在完成功能实现后忘记检查问题。
如果您零星地使用这些工具,它们发现的许多问题将需要您调查原因,并在过程中花费大量时间。
其他工具,例如 FBRetainCycleDetector,依赖于 objc 运行时魔法来查找问题,但这意味着它们实际上不能用于纯 Swift 类。这个小工具只专注于跟踪对象的生命周期,这意味着它可以用于 Objective-C 和 Swift 代码库,并且它不依赖于任何复杂的或自动的魔法行为。
如果您想支持我的工作并改进您的工程工作流程,请查看我的 SwiftyStack 课程
将 pod 'LifetimeTracker'
添加到您的 Podfile。
将 github "krzysztofzablocki/LifetimeTracker"
添加到您的 Cartfile。
将 LifetimeTracker"
添加到您的 Package.swift 的 dependencies 值中。
dependencies: [
.package(url: "https://github.com/krzysztofzablocki/LifetimeTracker.git", .upToNextMajor(from: "1.8.0"))
]
要集成可视化通知,只需在 AppDelegate(didFinishLaunchingWithOptions:)
的开头添加以下行,如果您使用的是 iOS 13+ SceneDelegates,则在 scene(willConnectTo:options:)
中添加。
Swift
#if DEBUG
LifetimeTracker.setup(
onUpdate: LifetimeTrackerDashboardIntegration(
visibility: .alwaysVisible,
style: .bar,
textColorForNoIssues: .systemGreen,
textColorForLeakDetected: .systemRed
).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
个活跃项目时,跟踪器会通知您。
您需要遵循 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()
}
}
您需要遵循 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,您可以使用它为每个 PR 添加复选框,以确保人们已验证没有创建 retain cycle,并且在有人忘记调用 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
有时,获取有关最后一个 retain cycle 的信息以将其记录到外部来源(例如分析/跟踪器)非常有用。为了做到这一点,我们可以使用 onLeakDetected
更新初始配置
[LifetimeTracker setupOnLeakDetected:^(Entry * entry, EntriesGroup * group) {
NSLog(@"POSSIBLE LEAK ALERT: %@ - current count %li, max count %li", entry.name, (long)entry.count, (long)entry.maxCount);
} onUpdate:^(NSDictionary<NSString *,EntriesGroup *> * groups) {
[dashboardIntegration refreshUIWithTrackedGroups: groups];
}];
LifetimeTracker.setup(onLeakDetected: { entity, _ in
log.warning("POSSIBLE LEAK ALERT: \(entity.name) - current count: \(entity.count), max count: \(entity.maxCount)")
}, onUpdate: LifetimeTrackerDashboardIntegration(visibility: .alwaysVisible, style: .bar).refreshUI)
您可以将跟踪的对象分组在一起。默认情况下,组的 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。