brew install peripheryapp/periphery/periphery
mint install peripheryapp/periphery
scan
Команда сканирования — основная функция периферии. Чтобы начать пошаговую настройку, просто перейдите в каталог проекта и запустите:
periphery scan --setup
Пошаговая настройка работает только для проектов Xcode и SwiftPM. Чтобы использовать Periphery с системами сборки сторонних производителей, такими как Bazel, см. раздел «Системы сборки».
Ответив на несколько вопросов, Periphery распечатает команду полного сканирования и выполнит ее.
Пошаговая настройка предназначена только для ознакомительных целей. Как только вы освоитесь с периферией, вы сможете попробовать некоторые более сложные параметры, все из которых можно увидеть с помощью periphery help scan
.
Чтобы получить последовательные результаты от периферии, крайне важно понимать последствия целей сборки, которые вы выбираете для анализа. Например, представьте себе проект, состоящий из трех целей: App, Lib и Tests. Цель «Приложение» импортирует Lib, а цель «Тесты» импортирует и приложение, и библиотеку. Если вы укажете все три параметра --targets
, то Periphery сможет проанализировать ваш проект в целом. Однако если вы решите анализировать только приложения и библиотеки, но не тесты, Periphery может сообщить о некоторых экземплярах неиспользуемого кода, на которые ссылаются только тесты. Поэтому, если вы подозреваете, что Периферия предоставила неправильный результат, важно учитывать цели, которые вы выбрали для анализа.
Если ваш проект состоит из одной или нескольких автономных платформ, которые также не содержат каких-либо приложений, использующих их интерфейсы, вам нужно будет указать Periphery, чтобы она предполагала, что все публичные объявления фактически используются, включая --retain-public
вариант.
Для проектов, в которых используются смешанные Objective-C и Swift, настоятельно рекомендуется прочитать о том, как это может повлиять на ваши результаты.
После того, как вы выбрали подходящие параметры для своего проекта, вы можете сохранить их в файле конфигурации YAML. Самый простой способ добиться этого — запустить Periphery с опцией --verbose
. В начале вывода вы увидите раздел [configuration:begin]
с вашей конфигурацией в формате YAML ниже. Скопируйте и вставьте конфигурацию в .periphery.yml
в корне папки вашего проекта. Теперь вы можете просто запустить periphery scan
, и будет использована конфигурация YAML.
Периферия сначала строит ваш проект. Для проектов Xcode схемы, предоставляемые опцией --schemes
, создаются с использованием xcodebuild
. Для проектов Swift Package Manager отдельные цели, предоставляемые с помощью опции --targets
, создаются с использованием swift build
. Компилятор Swift использует метод, называемый индексированием во время построения, для заполнения хранилища индексов, содержащего информацию о структуре исходного кода вашего проекта.
После того, как ваш проект создан, Periphery выполняет этап индексирования. Для каждого исходного файла, который является членом целевых объектов, предоставляемых с помощью опции --targets
, Periphery получает структурную информацию из хранилища индексов и строит собственное внутреннее графическое представление вашего проекта. Периферия также анализирует абстрактное синтаксическое дерево (AST) каждого файла, чтобы заполнить некоторые детали, не предоставленные индексным хранилищем.
После завершения индексации Periphery анализирует график, чтобы выявить неиспользуемый код. Этот этап состоит из ряда шагов, которые изменяют граф, чтобы упростить идентификацию конкретных сценариев неиспользуемого кода. Последний шаг просматривает граф от его корней, чтобы определить объявления, на которые больше нет ссылок.
Цель Periphery — сообщать о случаях неиспользованных объявлений . Объявление — это class
, struct
, protocol
, function
, property
, constructor
, enum
, typealias
, associatedtype
и т. д. Как и следовало ожидать, Periphery может идентифицировать простые объявления, на которые нет ссылок, например class
, который больше не используется нигде в вашем файле. кодовая база.
Периферия также может идентифицировать более сложные экземпляры неиспользуемого кода. В следующем разделе это объясняется подробно.
Периферия может идентифицировать неиспользуемые параметры функции. Экземпляры неиспользуемых параметров также могут быть идентифицированы в протоколах и соответствующих им объявлениях, а также параметры в переопределенных методах. Оба этих сценария описаны ниже.
О неиспользуемом параметре функции протокола будет сообщено как о неиспользуемом только в том случае, если этот параметр также не используется во всех реализациях.
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. " )
}
}
Кончик
Вы можете игнорировать все неиспользуемые параметры протоколов и соответствующих функций с помощью опции
--retain-unused-protocol-func-params
.
Подобно протоколам, параметры переопределенных функций сообщаются как неиспользуемые только в том случае, если они также не используются в базовой функции и во всех переопределяющих функциях.
class BaseGreeter {
func greet ( name : String ) {
print ( " Hello. " )
}
func farewell ( name : String ) { // 'name' is unused
print ( " Goodbye. " )
}
}
class InformalGreeter : BaseGreeter {
override func greet ( name : String ) {
print ( " Sup " + name + " . " )
}
override func farewell ( name : String ) { // 'name' is unused
print ( " Cya. " )
}
}
Неиспользуемые параметры протоколов или классов, определенных во сторонних модулях (например, Foundation), всегда игнорируются, поскольку у вас нет доступа для изменения объявления базовой функции.
Неиспользуемые параметры функций, которые просто вызывают fatalError
также игнорируются. Такие функции часто являются нереализованными необходимыми инициализаторами в подклассах.
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 ) {
fatalError ( " init(param:) has not been implemented " )
}
}
Протокол, которому соответствует объект, по-настоящему не используется, если он не используется также как экзистенциальный тип или для специализации универсального метода/класса. Периферия способна идентифицировать такие избыточные протоколы, независимо от того, соответствуют ли они одному или даже нескольким объектам.
protocol MyProtocol { // 'MyProtocol' is redundant
func someMethod ( )
}
class MyClass1 : MyProtocol { // 'MyProtocol' conformance is redundant
func someMethod ( ) {
print ( " Hello from MyClass1! " )
}
}
class MyClass2 : MyProtocol { // 'MyProtocol' conformance is redundant
func someMethod ( ) {
print ( " Hello from MyClass2! " )
}
}
let myClass1 = MyClass1 ( )
myClass1 . someMethod ( )
let myClass2 = MyClass2 ( )
myClass2 . someMethod ( )
Здесь мы видим, что, несмотря на то, что обе реализации someMethod
вызываются, объект ни в коем случае не принимает тип MyProtocol
. Следовательно, сам протокол является избыточным, и нет никакой пользы от соответствия ему MyClass1
или MyClass2
. Мы можем удалить MyProtocol
вместе с каждым избыточным соответствием и просто сохранить someMethod
в каждом классе.
Как и обычный метод или свойство объекта, отдельные свойства и методы, объявленные вашим протоколом, также могут быть идентифицированы как неиспользуемые.
protocol MyProtocol {
var usedProperty : String { get }
var unusedProperty : String { get } // 'unusedProperty' is unused
}
class MyConformingClass : MyProtocol {
var usedProperty : String = " used "
var unusedProperty : String = " unused " // 'unusedProperty' is unused
}
class MyClass {
let conformingClass : MyProtocol
init ( ) {
conformingClass = MyConformingClass ( )
}
func perform ( ) {
print ( conformingClass . usedProperty )
}
}
let myClass = MyClass ( )
myClass . perform ( )
Здесь мы видим, что MyProtocol
сам по себе используется и не может быть удален. Однако, поскольку unusedProperty
никогда не вызывается в MyConformingClass
, периферия может определить, что объявление unusedProperty
в MyProtocol
также не используется и может быть удалено вместе с неиспользуемой реализацией unusedProperty
.
Помимо возможности идентифицировать неиспользуемые перечисления, Periphery также может идентифицировать отдельные неиспользуемые случаи перечисления. Простые перечисления, которые не являются представимыми в необработанном виде, т. е. не имеют типа значения String
, Character
, Int
или с плавающей запятой, могут быть надежно идентифицированы. Однако перечисления, которые имеют необработанный тип значения, могут быть динамическими по своей природе, и поэтому их следует предполагать как используемые.
Давайте проясним это на кратком примере:
enum MyEnum : String {
case myCase
}
func someFunction ( value : String ) {
if let myEnum = MyEnum ( rawValue : value ) {
somethingImportant ( myEnum )
}
}
Прямой ссылки на случай myCase
нет, поэтому разумно ожидать, что он может больше не понадобиться, однако, если он будет удален, мы увидим, что somethingImportant
никогда не будет вызываться, если someFunction
будет передано значение "myCase"
.
Свойства, которые назначены, но никогда не используются, идентифицируются как таковые, например:
class MyClass {
var assignOnlyProperty : String // 'assignOnlyProperty' is assigned, but never used
init ( value : String ) {
self . assignOnlyProperty = value
}
}
В некоторых случаях это может быть запланированное поведение, поэтому у вас есть несколько вариантов отключить такие результаты:
--retain-assign-only-property-types
. Указанные типы должны точно соответствовать их использованию в объявлении свойства (без необязательного вопросительного знака), например String
, [String]
, Set
. Периферия не может разрешить выведенные типы свойств, поэтому в некоторых случаях вам может потребоваться добавить явные аннотации типов к вашим свойствам.--retain-assign-only-properties
. Объявления, которые помечены как public
, но на которые не ссылаются из-за пределов их домашнего модуля, считаются имеющими избыточную общедоступную доступность. В этом случае public
аннотацию можно удалить из объявления. Удаление избыточной публичной доступности имеет несколько преимуществ:
final
, автоматически обнаруживая все потенциально переопределяющие объявления. final
классы лучше оптимизируются компилятором. Этот анализ можно отключить с помощью --disable-redundant-public-analysis
.
Периферия может обнаружить неиспользованный импорт целей, которые она просканировала, то есть тех, которые указаны с помощью аргумента --targets
. Он не может обнаружить неиспользуемый импорт других целей, поскольку исходные файлы Swift недоступны, а использование @_exported
невозможно наблюдать. @_exported
проблематичен, поскольку он изменяет общедоступный интерфейс цели так, что объявления, экспортированные целью, больше не обязательно объявляются импортированной целью. Например, цель Foundation
экспортирует Dispatch
среди других целей. Если какой-либо исходный файл импортирует Foundation
и ссылается на DispatchQueue
, но не на другие объявления из Foundation
, то импорт Foundation
не может быть удален, так как это также сделает тип DispatchQueue
недоступным. Поэтому, чтобы избежать ложных срабатываний, Periphery обнаруживает только неиспользованный импорт целей, которые она отсканировала.
Периферия, скорее всего, выдаст ложные срабатывания для целей со смешанным Swift и Objective-C, поскольку периферия не может сканировать файлы Objective-C. Поэтому рекомендуется отключить обнаружение неиспользуемого импорта для проектов со значительным объемом Objective-C или вручную исключить смешанные языковые цели из результатов.
Периферия не может анализировать код Objective-C, поскольку типы могут быть динамически типизированы.
По умолчанию Periphery не предполагает, что используются объявления, доступные среде выполнения Objective-C. Если ваш проект представляет собой смесь Swift и Objective-C, вы можете включить это поведение с помощью опции --retain-objc-accessible
. Объявления Swift, доступные среде выполнения Objective-C, — это объявления, явно помеченные @objc
или @objcMembers
, а также классы, которые наследуют NSObject
прямо или косвенно через другой класс.
Альтернативно, --retain-objc-annotated
можно использовать только для сохранения объявлений, которые явно помечены @objc
или @objcMembers
. Типы, наследующие NSObject
не сохраняются, если у них нет явных аннотаций. Эта опция может раскрыть больше неиспользуемого кода, но с оговоркой, что некоторые результаты могут быть неверными, если объявление действительно используется в коде Objective-C. Чтобы устранить эти неверные результаты, вы должны добавить к объявлению аннотацию @objc
.
Swift синтезирует дополнительный код для типов Codable
, который не виден периферии и может привести к ложным срабатываниям для свойств, на которые напрямую не ссылаются из несинтезированного кода. Если ваш проект содержит много таких типов, вы можете сохранить все свойства типов Codable
с помощью --retain-codable-properties
. Альтернативно, вы можете сохранить свойства только для Encodable
типов с помощью --retain-encodable-properties
.
Если соответствие Codable
объявлено протоколом во внешнем модуле, который не сканируется Periphery, вы можете дать указание Periphery идентифицировать протоколы как Codable
с помощью --external-codable-protocols "ExternalProtocol"
.
Любой класс, наследующий XCTestCase
автоматически сохраняется вместе со своими тестовыми методами. Однако, когда класс наследует XCTestCase
косвенно через другой класс, например UnitTestCase
, и этот класс находится в цели, которая не сканируется периферией, вам необходимо использовать параметр --external-test-case-classes UnitTestCase
, чтобы указать периферии рассматривать UnitTestCase
как подкласс XCTestCase
.
Если ваш проект содержит файлы Interface Builder (например, раскадровки и XIB), Periphery примет их во внимание при выявлении неиспользуемых объявлений. Однако в настоящее время Periphery идентифицирует только неиспользуемые классы. Это ограничение существует потому, что Periphery еще не полностью анализирует файлы Interface Builder (см. проблему № 212). В соответствии с принципом проектирования Periphery, направленным на предотвращение ложных срабатываний, предполагается, что если на класс есть ссылка в файле Interface Builder, используются все его IBOutlets
и IBActions
, даже если они могут не существовать в действительности. Этот подход будет пересмотрен для точного определения неиспользуемых IBActions
и IBOutlets
, как только Periphery получит возможность анализировать файлы Interface Builder.
По какой-то причине вы можете захотеть сохранить некоторый неиспользуемый код. Команды комментариев исходного кода можно использовать для игнорирования определенных объявлений и исключения их из результатов.
Команда игнорировать комментарий может быть помещена непосредственно в строку над любым объявлением, чтобы игнорировать его и все дочерние объявления:
// periphery:ignore
class MyClass { }
Вы также можете игнорировать определенные неиспользуемые параметры функции:
// periphery:ignore:parameters unusedOne,unusedTwo
func someFunc ( used : String , unusedOne : String , unusedTwo : String ) {
print ( used )
}
Команду // periphery:ignore:all
можно поместить в начало исходного файла, чтобы игнорировать все содержимое файла. Обратите внимание, что комментарий должен быть размещен над любым кодом, включая операторы импорта.
Команды комментариев также поддерживают завершающие комментарии после дефиса, так что вы можете включить объяснение в той же строке:
// periphery:ignore - explanation of why this is necessary
class MyClass { }
Прежде чем настраивать интеграцию Xcode, мы настоятельно рекомендуем вам сначала заставить Periphery работать в терминале, поскольку вы будете использовать ту же самую команду через Xcode.
Выберите свой проект в Навигаторе проектов и нажмите кнопку + в левом нижнем углу раздела «Цели». Выберите «Кросс-платформенный» и выберите «Агрегировать» . Нажмите Далее.
«Периферия» — это увлеченный проект, который требует огромных усилий для поддержания и развития. Если вы считаете Periphery полезным, рассмотрите возможность спонсирования через спонсоров GitHub.
Особая благодарность выражается следующим щедрым спонсорам:
SaGa Corp разрабатывает уникальные технологии для финансовых игроков и их клиентов.
Emerge Tools — это набор революционных продуктов, призванных повысить эффективность мобильных приложений и команд, которые их создают.