# LifetimeTracker: catching retain cycles while you still remember the feature

> LifetimeTracker is a Swift and Objective-C library that counts live instances of the classes you mark as trackable and shows a dashboard when more of them survive than you expect. It is a development-time signal, not a leak scanner, and that distinction decides where it fits.

**krzysztofzablocki/LifetimeTracker** — Find retain cycles / memory leaks sooner.

- Repository: https://github.com/krzysztofzablocki/LifetimeTracker
- Website: http://merowing.info
- Stars: 3,310 · Forks: 154
- Language: Swift
- License: MIT
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/krzysztofzablocki-lifetimetracker

## The gap LifetimeTracker fills between Instruments and code review

Instruments and the Memory Graph Debugger answer the question "is something leaking?" accurately, but they answer it only when a developer remembers to ask. The README states the problem plainly: those tools are used sporadically, and by the time an issue surfaces, tracing its cause costs time. LifetimeTracker moves the question earlier, into the moment a feature is closed, by keeping a running count of how many instances of a given class are alive. When that count exceeds a configured maximum, the tracker reports it. The intended audience is iOS teams working in Swift or Objective-C who want a low-effort signal during development rather than a forensic session after a bug report. The README also draws a boundary against FBRetainCycleDetector: that tool relies on Objective-C runtime behaviour, which the README says makes it unusable for pure Swift classes. LifetimeTracker instead observes object lifetime, so it works across both languages without runtime introspection.

## How the tracking mechanism works: conformance, configuration, and a live count

There is no static analysis and no swizzling. A class opts in by conforming to LifetimeTrackable and declaring a lifetimeConfiguration that sets a maxCount and a groupName. The class then calls trackLifetime() at the end of its init. From that point the tracker knows an instance exists and attributes it to a group. When the number of live instances in a group exceeds maxCount, that group is reported as an issue. The README's example uses maxCount: 1 and groupName: "VC" for a view controller, which encodes the expectation that exactly one instance of that controller should be alive at a time. The dashboard is a separate concern: LifetimeTracker.setup(onUpdate:) receives the tracked groups and the dashboard integration refreshes the UI. Visibility is one of three values (alwaysVisible, alwaysHidden, visibleWithIssuesDetected) and the presentation is either a bar overlay listing issues or a circular indicator that opens the list modally. Because the count is driven by your own annotations, the tool's coverage is exactly the set of classes you chose to annotate. That is the design's main strength and its main blind spot.

## Installing LifetimeTracker and getting a first count on screen

The README lists three distribution routes. CocoaPods takes pod 'LifetimeTracker' in a Podfile. Carthage takes github "krzysztofzablocki/LifetimeTracker" in a Cartfile. Swift Package Manager takes a package dependency; the README's snippet pins .upToNextMajor(from: "1.8.0").

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

After the dependency resolves, wire the dashboard once at launch. The README places this at the start of AppDelegate(didFinishLaunchingWithOptions:) or, on iOS 13 and later, in scene(willConnectTo:options:). Note that the README wraps this call in #if DEBUG.

```swift
#if DEBUG
	LifetimeTracker.setup(
        onUpdate: LifetimeTrackerDashboardIntegration(
            visibility: .alwaysVisible,
            style: .bar,
            textColorForNoIssues: .systemGreen,
            textColorForLeakDetected: .systemRed
        ).refreshUI
    )
#endif
```

Then mark one class as trackable. The README's pattern is conformance plus a configuration plus a call at the end of init.

```swift
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()
    }
}
```

With visibility set to .alwaysVisible and style .bar, you should see the bar overlay on screen while the app runs, showing the tracked groups and their counts. Push and pop the controller and watch whether the count returns to one. Objective-C follows the same shape: conform to LifetimeTrackable, return a LifetimeConfiguration from a class method, and call [self trackLifetime] at the end of the initializer.

## Where LifetimeTracker gives you nothing, and where it can mislead

The tool reports counts, not causes. A group exceeding maxCount tells you that instances are surviving, but the README documents no stack trace, no object graph, and no reference path for the leaked instance. Finding the actual cycle still means reproducing the scenario under Instruments or the Memory Graph Debugger. The second limitation is coverage: a class that never calls trackLifetime() is invisible, and a class whose initializer path does not reach that call silently escapes tracking. The README's own Danger integration exists because of this failure mode, using a script that fails a pull request when a file contains LifetimeTrackable but no trackLifetime() call, and warns when a View or ViewModel implements neither. That recipe is also a hint about the work involved: the sample script skips files whose names contain Node, Tests, or FlowCoordinator, so any team adopting it will need to adjust those filename heuristics to their own conventions. Finally, maxCount is a manual expectation. Set it too high and real leaks pass; set it too low and legitimate concurrent instances produce noise. There is no automatic baseline.

## LifetimeTracker compared with FBRetainCycleDetector

The README positions these two tools as different approaches rather than competing versions of the same thing. FBRetainCycleDetector inspects the Objective-C runtime to find the cycle itself, which the README says means it cannot really be used for pure Swift classes. LifetimeTracker does the opposite: instead of finding the cycle, it watches how long objects live, which is why the README says it works in both Objective-C and Swift codebases and relies on no complex or automatic behaviour. The practical difference is what you get and what you pay. FBRetainCycleDetector can point at a cycle; LifetimeTracker tells you a count is wrong and leaves the diagnosis to you. In exchange, LifetimeTracker asks for explicit annotations and imposes no runtime introspection. If your codebase is predominantly Objective-C and you want the cycle identified for you, the runtime approach may suit better. If you write Swift and want a signal that appears during normal development rather than during a dedicated investigation, the lifetime-counting approach is the one that fits.

## Maintenance, licence, and what upgrading costs

The repository is not archived and the last push was on 2026-03-05, which is the same date as the 1.8.6 release. The release history shows 1.8.4 in May 2024, 1.8.5 in June 2025, and 1.8.6 in March 2026, so the cadence is roughly annual rather than continuous. Plan upgrades accordingly: there is no sign of a fast-moving API surface, but there is also no sign of frequent maintenance windows. The licence is MIT, which permits use in proprietary applications; that is a statement about the licence text, not legal advice, and teams with unusual distribution or attribution requirements should read the LICENSE file in the repository. The upgrade cost that matters most here is not the library version but your annotations. Every class you mark with LifetimeTrackable and every maxCount you choose becomes part of your codebase's expectations, and those expectations have to be revisited when a screen legitimately starts holding two instances. The Danger script is the piece most likely to need local editing, since its filename rules are written for one project's conventions.

## Conclusion

Adopt LifetimeTracker if your team ships iOS features in short cycles and keeps forgetting to open Instruments or the Memory Graph Debugger before merging. Do not adopt it expecting automatic detection: it only sees classes you annotate with LifetimeTrackable and call trackLifetime() on, so unannotated types stay invisible. Before rolling it out, verify that your initializers actually reach trackLifetime() (the Danger recipe exists precisely because they often do not), and confirm which build configurations include the setup call, since the README's integration example wraps it in #if DEBUG.

## FAQ

### What is LifetimeTracker?

It is a Swift and Objective-C library that tracks the lifetime of objects you mark as trackable and surfaces retain cycle or memory issues during development. When more instances of a group are alive than the configured maxCount, the tracker reports it in an on-screen dashboard.

### Does LifetimeTracker work with pure Swift classes?

Yes. The README contrasts it with FBRetainCycleDetector, which relies on Objective-C runtime behaviour and therefore cannot really be used for pure Swift classes. LifetimeTracker tracks object lifetime instead, so it applies to both Objective-C and Swift code.

### How do I install LifetimeTracker?

The README lists CocoaPods, Carthage, and Swift Package Manager. For Swift Package Manager it gives a dependency on https://github.com/krzysztofzablocki/LifetimeTracker.git with .upToNextMajor(from: "1.8.0").

### Why does LifetimeTracker not show an issue for a class I expect it to track?

Tracking is opt-in. A class must conform to LifetimeTrackable, provide a lifetimeConfiguration, and call trackLifetime() at the end of its initializer. The README's Danger recipe fails a pull request when a file contains LifetimeTrackable but no trackLifetime() call, which is the same gap.

## Sources

- [krzysztofzablocki/LifetimeTracker on GitHub](https://github.com/krzysztofzablocki/LifetimeTracker)
- [License: MIT](https://github.com/krzysztofzablocki/LifetimeTracker/blob/master/LICENSE)
- [Project website](http://merowing.info)
- [README](https://github.com/krzysztofzablocki/LifetimeTracker/blob/master/README.md)
- [Releases](https://github.com/krzysztofzablocki/LifetimeTracker/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/krzysztofzablocki-lifetimetracker
