Finding Dead Code in Your iOS/macOS Project
We’ve all been there. Your iOS/macOS project starts small and clean, but after months of feature additions, refactors, and experiments, you end up with code that nobody’s sure is actually being used anymore. Old helper functions from that feature you scrapped, imports you’re pretty sure aren’t needed but too afraid to remove.
I regularly use Periphery to clean up my projects. Here’s what I’ve learned so far.
I’ll walk you through a simple setup and usage guide for Periphery:
What is Periphery? #
Periphery is a tool that scans your Swift codebase and identifies unused code. It finds unused classes, structs, functions, properties, protocols, and even imports. Think of it as a garbage collector for your source code.
Unlike manual code reviews where you might miss things, Periphery analyzes your entire project systematically. It looks at what’s actually being called and referenced, not just what exists.
Installation #
Getting Periphery set up is straightforward. I used Homebrew:
brew install periphery
You can also install it via Mint if that’s your thing:
mint install peripheryapp/periphery
That’s it. No complicated setup or configuration files required to get started.
Configuration #
Here’s where things get interesting. Periphery works out of the box, but you’ll want to configure it for better results and to avoid false positive results as much as possible.
First, make sure your project builds successfully. Periphery uses your build log to understand your code structure, so a clean build is essential.
Run the setup command:
periphery scan --setup
This creates a .periphery.yml
file in your project root. Here’s what mine looked like:
workspace: MyApp.xcworkspace
schemes:
- MyApp
retain_public: true
retain_objc_accessible: true
You can customize this file based on your project’s needs.
Key Configuration Options #
retain_public - Keep this true if you’re building a framework or have public APIs. You don’t want to delete code that external consumers might use.
retain_objc_accessible - Essential if you’re working with UIKit or have any @objc code. Periphery can’t always track Objective-C usage patterns.
targets - Specify which targets to scan. Start with your main app target to keep things manageable:
targets:
- MyApp
ignore - Exclude files or folders you know are fine:
ignore:
- Tests/**
- Pods/**
If you want to modify and customize more details, you can check the documentation here: https://github.com/peripheryapp/periphery
Usage #
Once configured, scanning is simple:
periphery scan
The first scan will probably overwhelm you. That’s normal. Here’s what I saw on my first run:
warning: Class 'OldSettingsViewController' is unused
warning: Function 'DateHelper.isWeekend' is unused
warning: Property 'UserProfile.createdAt' is unused
warning: Import 'Combine' is unused in File.swift
...and 50 more warnings
Don’t panic. Not everything flagged is actually unused. You’ll need to optimize the YML file based on your needs.
Working Through Results #
I took it slow and focused on one category at a time:
- Entire files - These are usually safe to delete. If a whole class isn’t used anywhere, it’s dead weight.
- Functions and properties - Review these carefully. Sometimes they look unused but are called via protocols or delegates.
- Imports - Easy wins. If you’re not using Foundation members in a file, remove it.
Dealing with False Positives #
You’ll hit false positives, especially with:
- SwiftUI views (sometimes Periphery misses usage)
- Protocol conformances
- Code called from Objective-C
- XIB/Storyboard connections
For these, add them to your ignore list or use inline comments:
// periphery:ignore
class MyViewController: UIViewController { }
Benefits #
After cleaning up my project based on Periphery’s findings, here’s what improved:
- Faster builds - Removed 30+ unused files. My incremental builds got noticeably quicker.
- Smaller binary - Less code means smaller app size. Not dramatic, but every KB counts.
- Easier navigation - No more stumbling into old, unused code when searching the project.
- Confidence in refactoring - Knowing what’s actually used makes it safer to change things.
Considering Facts #
Before you go deleting everything Periphery flags, keep these in mind:
It’s not perfect - Periphery relies on static analysis. Dynamic code, reflection, and string-based lookups can fool it.
Public APIs need care - If you’re building a framework, don’t blindly delete public methods just because your tests don’t use them.
Test coverage matters - Periphery can find unused test helpers, but make sure you’re not removing tests themselves by mistake.
Build configuration matters - Different build configs might use different code. Make sure you scan the right scheme.
Time investment - The first cleanup takes time. Reviewing results, testing changes, and tuning config isn’t instant. Budget a few hours.
I’d also recommend:
- Run it on a feature branch first
- Review changes carefully before committing
- Test thoroughly after removing code
- Start with obvious wins (whole unused files) before tackling smaller items
Summary & Suggestions #
Periphery is a solid tool for finding dead code in Swift projects. It’s not magic, and you’ll need to configure it properly, but the results are worth it. Also, with a proper Python or Ruby script, you can convert Periphery’s JSON results into a much more readable format. If you want to take things further, you can implement it in your CI or Danger to regularly see unused code statistics and amounts.
My Suggestions: #
Start small - Don’t try to fix everything in one go. Pick one module or target and clean that first.
Use version control - Make small commits. If something breaks, you can easily revert.
Run it regularly - Add it to your workflow before major releases or after big refactors. Don’t let dead code pile up again.
Configure for your project - Spend time tuning the config. A well-configured Periphery gives way more reliable results.
Combine with other tools - Use SwiftLint for style, Periphery for unused code. They complement each other.
Document exceptions - If you’re keeping something that looks unused, add a comment explaining why.
If your iOS/macOS project has been around for more than 6 months, you probably have unused code. Periphery will find it. Just be ready to spend some time reviewing results and configuring it for your specific setup.
The cleaner codebase is worth it.