LifetimeTracker can surface retain cycle / memory issues right as you develop your application

Overview

LifetimeTracker

Demo (bar) Demo (circular)
Bar style Circular style

LifetimeTracker can surface retain cycle / memory issues right as you develop your application, and it will surface them to you immediately, so you can find them with more ease.

Instruments and Memory Graph Debugger are great, but too many times, developers forget to check for issues as they close the feature implementation.

If you use those tools sporadically many of the issues they surface will require you to investigate the cause and cost you a lot of time in the process.

Other tools like FBRetainCycleDetector rely on objc runtime magic to find the problems, but that means they can't really be used for pure Swift classes. This small tool simply focuses on tracking lifetime of objects which means that it can be used in both Objective-C and Swift codebases, and it doesn't rely on any complex or automatic magic behaviour.

Installation

CocoaPods

Add pod 'LifetimeTracker' to your Podfile.

Carthage

Add github "krzysztofzablocki/LifetimeTracker" to your Cartfile.

Swift Package Manager

Add LifetimeTracker" to the dependencies value of your Package.swift.

dependencies: [
    .package(url: "https://github.com/krzysztofzablocki/LifetimeTracker.git", .upToNextMajor(from: "1.8.0"))
]

Integration

To Integrate visual notifications simply add following line at the start of AppDelegate(didFinishLaunchingWithOptions:) or if you are using iOS 13+ SceneDelegates in scene(willConnectTo:options:).

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

You can control when the dashboard is visible: alwaysVisible, alwaysHidden, or visibleWithIssuesDetected.

There are two styles available. A overlay bar view which shows the detailed list of issues directly on the screen or a circular view which displays only the amount of issues and opens the detailed list as modal view controller.

Tracking key actors

Usually, you want to use LifetimeTracker to track only key actors in your app, like ViewModels / Controllers etc. When you have more than maxCount items alive, the tracker will let you know.

Swift

You conform to LifetimeTrackable and call trackLifetime() at the end of your init functions:

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

You conform to LifetimeTrackable and call [self trackLifetime] at the end of your init functions:

@import LifetimeTracker;

@interface SectionFrontViewController() 

@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

Integrating with Danger

If you are using Danger, you can use it to add both checkboxes to each PR to ensure people have verified no retain cycles were created but also to inform you when someone forgets to call trackLifetime() function.

#
# ** 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

Surface last notification from the stack

Sometimes it is useful to get information about last retain cycle in order to log it to external sources such as analytics/trackers. In order to do that we can update initial configuration with onLeakDetected:

* groups) { [dashboardIntegration refreshUIWithTrackedGroups: groups]; }]; ">
[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)

Group tracked objects

You can group tracked objects together. maxCount of a group will be calculated by maxCount of all members per default. However, you can override it and provide a separate value to the group with groupMaxCount.

You may want to do this when you have a set of subclasses which can appear x times each, but in total only less than the sum of all subclasses:

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 ">
// 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

Writing integration tests for memory leaks

You can access the summary label using accessibility identifier LifetimeTracker.summaryLabel, which allows you to write integration tests that end up with looking up whether any issues were found.

License

LifetimeTracker is available under the MIT license. See LICENSE for more information.

Attributions

I've used SwiftPlate to generate xcodeproj compatible with CocoaPods and Carthage.

Comments
  • Leak memory when change root view controller

    Leak memory when change root view controller

    When i changed root view controller for my app. Life time tracker show 4 leak memory in my app. I don't know it is issue when set LifetimeConfiguration(maxCount: 1, groupName: "VC") in BaseViewController

    opened by phuongvnc 15
  • ViewController has called deinit, but still in couting.

    ViewController has called deinit, but still in couting.

    I have an VC, let say A.

    Root > push > A > then back (this works fine.)
    

    However

    Root > push > A > push B > back to A > back to Root
    

    the deinit is called at the end.

    but the counting is not getting it right.

    opened by libern 7
  • Add option to log leaks to external sources

    Add option to log leaks to external sources

    Introduction

    This proposal provides an opportunity to improve the existing surfacing mechanism of a retain cycle/memory issues to external sources, such as firebase analytics, datadog logging services, etc.

    Motivation

    Having UI indication of leaks/memory issues is not always sufficient, especially in production environments, expanding integration with an ability to log those issues can provide the ability to analyze incoming memory issues.

    Proposed solution

    The introduction of closure such as onLeakDetected can provide desired functionality without interfering with original designs. We can retrieve desired parameters such as instanceName | entry count | entry desiredCount to provide information about those leaks to external sources.

    So the final callback would look like the following code:

    LifetimeTracker.instance?.onLeakDetected = { instanceName, count, maxCount in
        print("POSSIBLE LEAK ALERT: \(instanceName) - current count: \(count), max count: \(maxCount)")
    }
    

    cc original author: @ericzelermyer

    opened by ignotusverum 6
  • Fix SwiftPM support on iOS now that SPM supports resources.

    Fix SwiftPM support on iOS now that SPM supports resources.

    Still need to do some testing but I think this should work. Putting it up sooner than later to get some feedback. I did not manually include the storyboards because SPM can figure those out on its own.

    To be done

    • Verify localized string stuff
    • Probably more work to do on mac to not include the storyboards..
    opened by noremac 6
  • Swift Package Manager Support for iOS

    Swift Package Manager Support for iOS

    Is far as I can see it is currently not possible to integrate the LifetimeTracker using the Swift Package Manager into an iOS application. The Package.swift tries to only include the iOS specific UI using the #if os(iOS) ... #else ... #endif but as the manifest is compiled for the host platform (see here), this doesn't work as expected.

    As the CocoaPods integration currently doesn't support macOS and there is no macOS "dashboard" #18, I would suggest to remove the #if os(iOS) condition and make the SwiftPM integration at least work for iOS.

    opened by ReneLindhorst 6
  • Ambiguous use of 'filter' error.

    Ambiguous use of 'filter' error.

    I'm getting an "Ambiguous use of filter" error in LifetimeTracker+DashboardView.swift.

    Any ideas?

    I'm running: Mac OS X 10.12.6 Xcode 9.2 Build version 9C40b

    screen shot 2018-03-10 at 4 43 36 pm
    opened by cmilr 5
  • Build Failed when try to install with carthage

    Build Failed when try to install with carthage

    Build Failed Task failed with exit code 65: /usr/bin/xcrun xcodebuild -project /Users/amine/Projets/Test/ios-wifi-connect/Carthage/Checkouts/LifetimeTracker/LifetimeTracker.xcodeproj -scheme LifetimeTracker-iOS -configuration Release -derivedDataPath /Users/amine/Library/Caches/org.carthage.CarthageKit/DerivedData/9.1_9B55/LifetimeTracker/1.2 -sdk iphoneos ONLY_ACTIVE_ARCH=NO BITCODE_GENERATION_MODE=bitcode CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES archive -archivePath ./ SKIP_INSTALL=YES GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=NO CLANG_ENABLE_CODE_COVERAGE=NO (launched in /Users/amine/Projets/Test/ios-wifi-connect/Carthage/Checkouts/LifetimeTracker)

    opened by amine2233 5
  • LifetimeTracker framework cannot be build on Xcode 9 / 9.1

    LifetimeTracker framework cannot be build on Xcode 9 / 9.1

    I used Carthage to build this framework, but it failed. Then I tried to use Xcode to build the cloning from Carthage, it still failed.

    I clone the v1.2 as well as master and try to build on Xcode 9/9.1. All failed by same errors. It is missing Constants type.

    See attachments.

    screen shot 2017-11-09 at 10 37 20 am bug 
    opened by huy-le 5
  • Unable to find a specification for `LifetimeTracker`

    Unable to find a specification for `LifetimeTracker`

    I added the pod 'LifetimeTracker' in Podfile. Upon hitting pod install command it says Unable to find a specification for LifetimeTracker

    What is the issue here?

    opened by rishabdutta 5
  • Real support for SwiftPackageManager

    Real support for SwiftPackageManager

    I tried to integrate the library using SwiftPackageManager and it fails, from just browsing the Sources I notices the library uses storyboards and xib files, only the XCode 12 beta supports packages with assets. How this library can support SPM then? Thank You for your help in advance, I can't use the Carthage package because it fails to compile with the new XCode 12 beta versions:

    • Build version 12A6163b
    opened by lecniers 4
  • Add SPM support for included UI elements

    Add SPM support for included UI elements

    This PR seeks to enable the LifetimeTracker UI elements in SPM builds. Currently attempting to use LifetimeTrackerDashboardIntegration will result in a crash.

    Main changes:

    • Bump swift-tools to 5.3
    • Move storyboards and xibs into resources and specify the resources folder in Swift.package
    • Add Bundle extension to handle logic for SPM vs. non-SPM usage
    • Ensure that all storyboards have customModuleProvider="target" removed (Inherit module from target)
    • Ensure that all storyboards have customModule="LifetimeTracker" set

    Will still need to be tested for non-SPM usage.

    opened by ltsuchiya 3
  • Supports staticlib (static library) needed

    Supports staticlib (static library) needed

    Our project is going to use LifetimeTracker for detect memory leak, and we love it. Thank you for creating this tool that elegant and effective for solving the hard problem.

    Our project use static library for dependencies. And we hope we can use LifetimeTracker as static library as well, but the UI part uses storyboards, so is there a workaround.

    opened by bohan-v 2
  • SwiftUIApp...

    SwiftUIApp...

    Hi,

    I tried to use that in SwiftUI with SwiftUIApp. I use like you told before in readme.md with ...willConnect and integration with WindowGroup...like this screenshots...but I didn't' t see anythings on app.

    Screenshot 2022-04-08 at 11 25 32

    Can you help me ? Thank for all job in Swift Open Community

    opened by pjcau 0
  • Adding ability to control red/green colors to make it more accessibility-friendly

    Adding ability to control red/green colors to make it more accessibility-friendly

    LifetimeTracker is brilliant! I love it :)

    One small suggestion I'd make is to make it possible for a developer to change the tint color of the text. This would be beneficial for developers who are a bit more visually impaired (so they can pick a green with their ideal contrast ratio), and for those who have red-green color blindness.

    Just a small suggestion! Keep it up 🥇

    opened by acosmicflamingo 1
  • Not displaying

    Not displaying

    I set it up like below

    #if DEBUG
    	LifetimeTracker.setup(onUpdate: LifetimeTrackerDashboardIntegration(visibility: .alwaysVisible, style: .bar).refreshUI)
    #endif
    

    and its not working

    opened by elemanhillary-zz 8
  • Xcode 10.2 / iOS 12.2 crash

    Xcode 10.2 / iOS 12.2 crash

    Hello @krzysztofzablocki

    With Xcode 10.2, compiling for and running on an iOS 12.2 device I got the following crash occurring:

    objc [1138]: Swift class extensions and categories on Swift classes are not allowed to have + load methods(Lldb).
    

    It looks like in our case removing LifetimeTracker seems to have sorted it out.

    Kind Regards,

    Goffredo

    opened by Panajev 0
Releases(1.8.1)
Owner
Krzysztof Zabłocki
Dev, Speaker, Creator. Currently Lead iOS @NYTimes My code powers up more than 70 000 apps.
Krzysztof Zabłocki
BFKit-Swift is a collection of useful classes, structs and extensions to develop Apps faster.

Features • Classes and Extensions Compatibility • Requirements • Communication • Contributing • Installing and Usage • Documentation • Changelog • Exa

Fabrizio Brancati 992 Dec 2, 2022
Swift library to develop custom Alexa Skills

AlexaSkillsKit AlexaSkillsKit is a Swift library that allows you to develop custom skills for Amazon Alexa, the voice service that powers Echo. It tak

Claus Höfele 170 Dec 27, 2022
BFKit is a collection of useful classes and categories to develop Apps faster.

Swift Version • What does it do • Language support • Requirements • Communication • Contributing • Installing and Usage • Documentation • Changelog •

Fabrizio Brancati 806 Dec 2, 2022
Pavel Surový 0 Jan 1, 2022
👷‍♀️ Closure-based delegation without memory leaks

Delegated 2.0 Delegated is a super small package that helps you avoid retain cycles when using closure-based delegation. New Medium post here. Origina

Oleg Dreyman 703 Oct 8, 2022
An application where users can simulate trading stocks with a starting balance of fake money.

Eighth Wonder Finance Table of Contents Overview Product Spec Video Walkthrough Wireframes Schema Overview Description An application where users can

Josh Harris 0 Dec 5, 2021
Ethereum Wallet Toolkit for iOS - You can implement an Ethereum wallet without a server and blockchain knowledge.

Introduction EtherWalletKit is an Ethereum Wallet Toolkit for iOS. I hope cryptocurrency and decentralized token economy become more widely adapted. H

Sung Woo Chang 136 Dec 25, 2022
Headline News Widget for Pock.You can display the articles fetched by rss.

Headline News Widget for Pock This is a headline news widget plugin for Pock You can display the articles fetched by rss. Demo In the demo video, the

null 11 Aug 30, 2022
Weather and forecasts for humans. Information you can act on.

Tropos Weather and forecasts for humans. Information you can act on. Most weather apps throw a lot of information at you but that doesn't answer the q

thoughtbot, inc. 1.5k Dec 28, 2022
LibAuthentication will simplify your code when if you want to use FaceID/TouchID in your tweaks.

LibAuthentication will simplify your code when if you want to use FaceID/TouchID in your tweaks.

Maximehip 6 Oct 3, 2022
The Objective-C block utilities you always wish you had.

BlocksKit Blocks in C and Objective-C are downright magical. They make coding easier and potentially quicker, not to mention faster on the front end w

BlocksKit 6.9k Dec 28, 2022
The simplest way to display the librarie's licences used in your application.

Features • Usage • Translation • Customisation • Installation • License Display a screen with all licences used in your application can be painful to

Florian Gabach 51 Feb 28, 2022
A visual developer tool for inspecting your iOS application data structures.

Tree Dump Debugger A visual developer tool for inspecting your iOS application data structures. Features Inspect any data structure with only one line

null 8 Nov 2, 2022
WhatsNewKit enables you to easily showcase your awesome new app features.

WhatsNewKit enables you to easily showcase your awesome new app features. It's designed from the ground up to be fully customized to your needs. Featu

Sven Tiigi 2.8k Jan 3, 2023
ALO sync allows you to sync resources form an ALO endpoint to your macOS file system.

ALO sync allows you to sync resources form an ALO endpoint to your macOS file system. Prerequisites macOS 11 No support for search* No suppor

Lawrence Bensaid 2 Jan 22, 2022
What if you could give your wallpapers, a little touch? On the fly, of course

Amēlija On the fly preferences. Features Custom Blurs for your LockScreen. Custom Blurs for your HomeScreen. Blur Types Epic (Gaussian). Dark. Light.

null 9 Dec 2, 2022
SuggestionsBox helps you build better a product trough your user suggestions. Written in Swift. 🗳

SuggestionsBox An iOS library to aggregate users feedback about suggestions, features or comments in order to help you build a better product. Swift V

Manuel Escrig 100 Feb 6, 2022
FancyGradient is a UIView subclass which let's you animate gradients in your iOS app. It is purely written in Swift.

FancyGradient is a UIView subclass which let's you animate gradients in your iOS app. It is purely written in Swift. Quickstart Static gradient let fa

Derek Jones 11 Aug 25, 2022