Swift: Unused Parameters

New in v1.2 is a feature many Swift developers may miss from their Objective-C days: unused function parameter detection!

Along with the simple cases of unused parameters, Periphery also applies some heuristics to give you actionable results in more complex cases.
Take this example:

protocol Greeter {
    func greet(name: String)
    func farewell(name: String) // 'name' is unused
}

class InformalGreeter: Greeter {
    func greet(name: String) {
      print("Sup " + name + ".")
    }

    func farewell(name: String) { // 'name' is unused
      print("Cya.")
    }
}

class FormalGreeter: Greeter {
  func greet(name: String) {
    print("Nice to meet you.")
  }

  func farewell(name: String) { // 'name' is unused
    print("Goodbye.")
  }
}

Here we can see that name is unused in all implementations of farewell, however it’s only used in one implementation of greet. Since removing the parameter from greet is not possible, Periphery does not warn about the unused parameter in FormalGreeter.greet.

Similar logic also applies to overridden methods, parameters are only identified as unused if they’re also unused in the base function and all overriding functions. Another scenario in which unactionable results are silenced is for functions that simply call fatalError(). Such functions often have a valid reason for not being implemented, as is the case for unused required initializers in subclasses:

class Base {
    let param: String

    required init(param: String) {
        self.param = param
    }
}

class Subclass: Base {
    init(custom: String) {
        super.init(param: custom)
    }

    required init(param: String) { // 'param' is not reported as unused
        fatalError("init(param:) has not been implemented")
    }
}

A primary goal of Periphery is to only produce actionable results, hopefully these heuristics deliver on that. Though if you’re not in the mood for altering the signatures of your protocol functions, you can tell Periphery to ignore all unused parameters from protocols and conforming functions with the --retain-unused-protocol-func-params option.

The scan command applies all of the heuristics described here. However if you’re in a hurry, the new scan-syntax command will perform basic parameter analysis in a fraction of the time. Beware though, scan-syntax does not apply the protocol & override heuristics!

In case you missed the previous announcement, Periphery now provides 100% of results for free - including unused parameter results. Installation is quick and painless, so give it a try!

100% of Results for Free

I’m happy to announce that Periphery now provides 100% of results for free!

In hindsight I wish I’d done this from the very beginning - but as they say, better late than never. This is an important milestone as it addresses a major concern I’d previously had - that open-source projects could not use Periphery without purchasing a license. With the old licensing model, the amount of work required to provide free access for OSS projects while protecting the integrity of the licensing mechanism was prohibitively high. This is a huge weight off my heart, and I hope many OSS projects can now benefit from Periphery.

Get Started Here.

I appreciate too that for some companies, unused code is perhaps more of an annoyance than a priority, and may be hard to justify spending money on to resolve. Ultimately I advocate for cleaner codebases, so to exclude some teams on the basis of money alone didn’t sit too well with me.

Along with these changes I’ve also introduced Pro Mode. Pro Mode is aimed at power users and teams that would like to integrate Periphery into their workflows. I believe free access to the basic operation of Periphery will also help inform development of new features in Pro Mode. You can learn more about the features of Pro Mode here.

I look forward to hearing from new users, and of the bountiful unused classes and functions you’ve removed. As you may be aware, Periphery is very security conscious software. I’ve deliberately avoided including any kind of analytics or crash reporting in Periphery in order to protect your intellectual property. So please share any feedback you may have with me at feedback@peripheryapp.com.

Happy hunting!

Swift: When Unused Code Is a Bug

Spot test - what’s the output of this code?

protocol Greeter {
    func greet()
}

extension Greeter {
    func greet() {
        print("Hello, World!")
    }
}

class BaseGreeter: Greeter {}

class LazyGreeter: BaseGreeter {
    func greet() {
        print("sup")
    }
}

let greeter: Greeter = LazyGreeter()
greeter.greet()

Even seasoned Swift developers may get this wrong, so don’t feel bad if your answer was “sup”. The actual output is “Hello, World!”, yes really… go ahead and run it in a Playground.

So what’s going on? It’s all down to how Swift handles dispatch of protocol types. A Protocol Witness Table1 (PWT) is the mechanism Swift uses to keep track of types that implement a protocol. When you call a function, or access a property on a protocol type, the PWT is used to identify the concrete implementation to call. The gotcha is that PWTs are only associated with types that declare their conformance to a protocol. Note that I said declare, and not simply conform. In this example, only BaseGreeter explicitly declares conformance to Greeter, thus the PWT is only associated with BaseGreeter. PWTs do not descend to subclasses, as subclasses do not explicitly declare their conformance. Therefore, since the greeter local let is of type Greeter - not LazyGreeter - the call to greet() defaults to the implementation in the Greeter extension, as there is no PWT associated with LazyGreeter.

To clarify, LazyGreeter().greet() does output “sup” as you’d expect. The issue is specifically the use of the Greeter protocol type for the local let that introduces indirection and the need for the PWT. In order for this example to output the expected “sup”, we’d need to implement greet() in BaseGreeter so that LazyGreeter can override it. That’s not ideal, and brings into question your reason for using a protocol extension in the first place.

Would you notice this bug during code review? I can’t confidently say that I would. Even more worrying, is that if you were to write a unit test for LazyGreeter, you’d likely just use let greeter = LazyGreeter(), thus bypassing Greeter and the PWT altogether. Both these points make this a particularly nasty gotcha, so keep your eyes out - you could well save yourself or a teammate from a lot of head scratching!

Fortunately Periphery has your back, and it’ll correctly report that LazyGreeter.greet() is unused. Granted, you may be a little confused at first and wonder if Periphery has made a mistake. This is a great example of why my unofficial slogan for Periphery is: “So accurate, you’ll think it’s broken”.

1: WWDC 2016: Understanding Swift Performance - PWT explanation begins at 24:30.

What's New in Version 0.8.0

Proof that your feedback does not fall on deaf ears - version 0.8.0 is out with some highly requested features.

First off, all you trail blazers out there will be pleased to hear that Periphery now supports Xcode 9’s new build system. It’ll be the default build system starting from Xcode 10, so I thought it best to get support in as early as possible. If you haven’t tried it out yet, maybe now is the time? You can carry on using Periphery exactly as you do with the current build system.


Previous releases didn’t support analyzing XCTest targets. My thinking was - “Code that’s only being used by a test case is code you don’t need.”. While that point of view is correct most of the time, there are a few scenarios where it’s perfectly valid to keep around some unused code (though hopefully only temporarily!). You can now pass test targets to the --targets option and they’ll be analyzed just like the others. Oh, and don’t worry - Periphery knows that all your XCTestCase subclass and test methods are in use.


Licenses can now be activated non-interactively, with a single command: periphery activate --email <email> --key <license key>. A welcome addition for those automating their use of Periphery.


Now, the coolest feature of this release - the diagnosis console! If you’ve ever questioned why Periphery didn’t report that a declaration was unused - yet you expected it to be - the diagnosis console can tell you the reason it’s still in use. Let’s use Periphery itself as an example. With the new --diagnose option, you’ll be greeted by a prompt after the results:

Welcome to the diagnosis console.
Type 'help' for instructions, ^C to exit.
>

The console has two commands: search and inspect. search allows you to search for declarations by name, e.g:

> search process
Found 1 declaration containing 'process':

[function.method.instance, 'process(_:)', private, [private], 's:12PeripheryKit16DiagnosisConsoleC7process33', DiagnosisConsole.swift:27:18]

Periphery’s code has a single declaration with a name containing ‘process’, and it’s the process(_:) method that handles input in the diagnosis console. Each declaration is represented as an array of details:

[<kind>, <name>, <accessibility>, <attributes>, <LLVM Unified Symbol Resolution (USR)>, <source location>]

We can now inspect this declaration using its unique USR.

> inspect 's:12PeripheryKit16DiagnosisConsoleC7process33'

Declaration hierarchy:

[function.method.instance, 'process(_:)', private, [private], 's:12PeripheryKit16DiagnosisConsoleC7process33', DiagnosisConsole.swift:27:18]
·· [class, 'DiagnosisConsole', public, [public], 's:12PeripheryKit16DiagnosisConsoleC', DiagnosisConsole.swift:3:14]

Active references:

[function.method.instance, 'process(_:)', 's:12PeripheryKit16DiagnosisConsoleC7process33', nil, DiagnosisConsole.swift:18:17]

We can see there’s a single reference to process(_:) at DiagnosisConsole.swift line 18, which happens to be:

17: if let command = readLine(strippingNewline: true) {
18:     process(command)
19: } else {

So we can see that the console is a useful tool for inspecting all active references to a particular declaration. However, Periphery must consider some declarations as being in use, even if there may not be any active references to them, i.e they are ‘retained’. The console will explain the reason:

> inspect 's:9periphery11HelpCommandV'
Declaration 'HelpCommand' does not have any active references, though it is retained because:
declaration is 'public' and the '--retain-public' option is in effect. Disable this behavior with '--no-retain-public'.

> inspect 'c:@M@periphery@objc(cs)ObjectInspector(im)'
Declaration 'ObjectInspector' does not have any active references, though it is retained because:
declaration is accessible by Objective-C and the '--retain-objc-annotated' option is in effect. Disable this behavior with '--no-retain-objc-annotated'.

> inspect 's:9periphery7versionyyF'
Declaration 'version()' does not have any active references, though it is retained because:
declaration is declared in main.swift.

> inspect 'c:@M@PeripheryKitTests@objc(cs)RetentionTest(im)testRetainedProtocolDoesNotRetainUnusedClass'
Declaration 'testRetainedProtocolDoesNotRetainUnusedClass()' does not have any active references, though it is retained because:
declaration is an XCTest test case or test method.

This is a pretty powerful feature, and we hope it can give you some deeper insight into the structure of your code.


Along with a couple of bug fixes, version 0.8.0 is the biggest release to date. The next few releases will focus on making Periphery even more convenient to use in Continuous Integration environments, watch this space!

To upgrade Periphery:

brew cask upgrade periphery

Case Study: Thumbtack, Inc.

Thumbtack are a market leader in connecting small business with customers in search of their services. They operate in all 50 U.S states, and offer access to over 1,000 service categories. Founded in 2008, they have developed iOS applications for their platform since 2014.

They provide two iOS applications: one for the small business owners, and the other for customers in search of services. Originally written in Objective-C, their applications are now over 85% converted to Swift. Both applications, along with 5 shared frameworks reside within a single Git repository. They use a single Xcode workspace to manage both applications, and CocoaPods for external dependency management.

Prior to Periphery, Thumbtack had been using the open-source fus tool to find some obvious cases of unused code. However, they found its analysis to be too limited, and false-positives an annoyance.

Thanks to their use of a single Xcode workspace, Periphery was able to analyze both applications and all shared frameworks as a single unit of code. In total they removed 4,156 lines of code, which equated to just under 3% of their total codebase.

In the near future Thumbtack plans to integrate Periphery into their Continuous Integration workflow, and further utilize Periphery once they have converted the remaining Objective-C to Swift.

Code is Risk

Imagine a parallel universe where all is the same, expect that software products don’t actually require any code to be written in order for them to exist. You dream up a product, say some magic words, and puff - it exists. A product managers dream, yet a software developers worst nightmare.

Granted, this is a totally absurd tweak to the laws of physics, but let me ask you a question. In this fictitious universe, you have the option for your product to just exist, or you can apply the traditional method of actually building it with code. Which method would you choose? Of course, anyone with a modicum of business acumen would choose the former. Now let’s bring this back to our reality, where we merely have a choice in the quality, and quantity of code it takes to build a product. There are numerous development practices that can produce more clean, succinct code with identical output to that of a more verbose implementation. But why put in the extra effort? Because code is risk. It’s somewhat of a paradox, but your code is a risk to the success of your product, while actually being your product.

Ask any battle hardened software developer about their past jobs, many will recount a story of the time they joined Dot Com Unicorn, Inc., only to find their product teams were more akin to firefighters than calm, methodical engineers. My personal anecdote is from 2013, when I was a Site Reliability Engineer (SRE) at LivingSocial. I was discussing with then CTO, Aaron Batalion, about the state of our codebase, and the effects of rapid growth. As an SRE, I was battling my fair share of fires, and many other teams were working hard to detangle our monolithic app into more distinct services. To me it felt we were paying a very high price for the technical debt the company had taken on. Perhaps a little more planning along the way could have left us in a better state, without affecting growth. Aaron’s position was that neither of us may have a job if the company hadn’t grown as quickly as it did. While he may have been right, it was clear to me that the current state of our codebase was a risk to our ability to maintain and grow our market position. We were simply not able to deliver on many requests from management because we were too busy refactoring critical subsystems.

When asked why we write unit tests for our code, many developers will reply that it can help reduce bugs. Bugs are unwanted because customers dislike them, therefore bugs are a risk to the success of a product. This may seem like a redundantly obvious statement, but risk is an important distinction.

When we start seeing code as risk, we take on a clearer perspective about the impact our decisions can have on the success of a product, rather than simply the quality of its code. Ask yourself: Is it worth the risk of using a “hack”, instead of spending a little more time to find a correct solution? Is it worth the risk not writing a unit test for a new feature? Is it worth the risk not waiting for a team member to review a change? In isolated situations it may make sense to take on the risk, but in general these kinds of risk should be avoided. Failure to be cognizant of risk on a daily basis can compound over time into a situation such as the one I experienced at LivingSocial.

Removal of unused code is one method of reducing risk. This article perfectly exemplifies the risk that unused code can pose. The New York Stock Exchange lost hundreds of millions of dollars by reusing a feature flag that awakened code that had not been used in years. The resulting downtime time cost them effectively $10 million per minute.

Many development practices designed to mitigate risk by producing more clean, succinct code can be very resource intensive, and sometimes even subjective. Removing unused code is comparatively straightforward, and requires very little effort. Regardless of the programming language you use, I strongly believe removing unused code should be a fundamental practice of professional software development.

For those using Swift, you may want to consider Periphery for identifying unused code in your projects.