LifetimeTracker 1.8.3

LifetimeTracker 1.8.3

测试已测试
语言语言 SwiftSwift
许可 MIT
发布上次发布2024年3月
SPM支持 SPM

Krzysztof ZabłockiHans Seiffert 维护。



LifetimeTracker

Demo (bar) Demo (circular)
条形样式 圆形样式

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];
}];

您可以通过设置参数来控制仪表板的可见性: alwaysVisiblealwaysHiddenvisibleWithIssuesDetected

提供了两种样式。一种是在屏幕上显示详细问题列表的覆盖条视图,另一种是以圆形视图显示问题数量,并作为模态视图控制器打开详细列表。

跟踪关键角色

通常您希望使用 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。