The goal of Periphery is to report instances of unused declarations. A declaration is a class, struct, protocol, function, property, constructor, enum, typealias or associatedtype. As you'd expect, Periphery is able to identify simple unreferenced declarations, i.e a class that is no longer used anywhere in your codebase.

This document aims to explain in detail the more advanced analysis techniques that Periphery employs.


A protocol which is conformed to by an object is not truly used unless it's also used in a type cast, as a property type, or to specialize a generic method/class, etc. Periphery is able to identify such protocols whether they are conformed to by one, or even multiple objects.

'MyProtocol' is unused
protocol MyProtocol { func someMethod() } class MyClass1: MyProtocol { func someMethod() { print("Hello from MyClass1!") } } class MyClass2: MyProtocol { func someMethod() { print("Hello from MyClass2!") } } let myClass1 = MyClass1() myClass1.someMethod() let myClass2 = MyClass2() myClass2.someMethod()

Here we can see that despite both implementations of someMethod are called, at no point does an object take on the type of MyProtocol. Therefore the protocol itself is redundant, and there's no benefit from MyClass1 or MyClass2 conforming to it. We can remove MyProtocol and just keep someMethod in each class.

Just like a normal method or property of a object, individual properties and methods declared by your protocol can also be identified as unused.

'unusedProperty' is unused
'unusedProperty' is unused
protocol MyProtocol { var usedProperty: String { get } var unusedProperty: String { get } } class MyConformingClass: MyProtocol { var usedProperty: String = "used" var unusedProperty: String = "unused" } class MyClass { let conformingClass: MyProtocol init() { conformingClass = MyConformingClass() } func perform() { print(conformingClass.usedProperty) } } let myClass = MyClass() myClass.perform()

Here we can see that MyProtocol is itself used, and cannot be removed. However, since unusedProperty is never called on MyConformingClass, Periphery is able to identify that the declaration of unusedProperty in MyProtocol is thus also unused and can be removed along with the unused implementation of unusedProperty.

Assign-only Properties

Properties which are assigned to but never read from can be identified as unused by Periphery. However, since an unread property may be a valid use-case, e.g to purposefully retain the object, this identification is only enabled in Aggressive Mode.

'myDependency' is unused
class MyClass { static func make() -> Self { return self.init(myDependency: inject()) } private let myDependency: MyDependency init(myDependency: MyDependency) { self.myDependency = myDependency } func someMethod() { } }

Note that this analysis only applies to simple properties, i.e properties do not define a custom getter or setter.

Removal of unused dependencies can reduce redundant incremental recompilation. The Swift compiler keeps track of every type that a source file exports, and uses (in .swiftdeps files). When a source file changes, any files that depend upon the changed types must also be recompiled. In this example, if MyDependency is declared in another file, and that file is changed, then the file containing MyClass will be needlessly recompiled.

As with any analysis enabled by aggressive mode, give extra consideration that the property might be needed solely to retain the instance. If the unread property is in fact needed, then this is a friendly reminder that you should add a comment explaining why it's needed.


Along with being able to identify unused enumerations, Periphery can also identify individual unused enum cases. Plain enums that are not raw representable, i.e that don't have a String, Character, Int or floating-point value type can be identified when Periphery is operating in non-aggressive mode. Enumerations that do have a raw value type can be dynamic in nature, and thus their identification is restricted to the aggressive mode only.

Let's clear this up with a quick example:

enum MyEnum: String {
  case myCase

func someFunction(value: String) {
  if let myEnum = MyEnum(rawValue: value) {

Since MyEnum has a raw value type of String, myCase is only identified as unused in aggressive mode. There's no direct reference to the case, so it's reasonable to expect it might no longer be needed, however if it were removed we can see that somethingImportant would never be called if someFunction were passed the value of "myCase". Therefore more scrutiny is advised when using aggressive mode and reviewing unused enum cases.


Since Objective-C can use dynamic types, Periphery cannot reason about it from a static standpoint. Therefore, by default, Periphery will assume that any declaration exposed to Objective-C is in use. If your project is 100% Swift, then you can disable this behavior with the --no-retain-objc-annotated option. For those using Periphery on a mixed project, there are some important implications to be aware of.

As you already know, any declaration that is annotated with @objc or @objcMembers is exposed to the Objective-C runtime, and Periphery will assume they are in use. However, you should also be aware that any class that inherits from NSObject is also implicitly exposed to Objective-C. If you ever come across a situation where Periphery reports that all methods and properties within a class - but not the class itself - are unused, then the class likely inherits from NSObject. It may be worth your time doing a cursory run of Periphery with --no-retain-objc-annotated, you may find a few extra declarations to remove. Though be warned, many declarations reported as unused may still be in use by Objective-C code, so you'll need to take extra care when reviewing them.

Global Equatable Operators

Periphery is currently unable to identify if an Equatable infix operator is in use if it is defined at global scope. For example:

class MyClass: Equatable {}

func == (lhs: MyClass, rhs: MyClass) -> Bool {
    return true

Therefore, by default, Periphery will assume all global Equatable infix operators are in use. However, when operating in Aggressive Mode, such operators will be reported as unused. Clearly, false negative results are unwanted, so you can resolve this by moving the operator within the class, or into an extension.

class MyClass {}

extension MyClass: Equatable {
    static func == (lhs: MyClass, rhs: MyClass) -> Bool {
        return true