brew install peripheryapp/periphery/periphery
mint install peripheryapp/periphery
scan
El comando de escaneo es la función principal de Periphery. Para comenzar una configuración guiada, simplemente cambie al directorio de su proyecto y ejecute:
periphery scan --setup
La configuración guiada solo funciona para proyectos Xcode y SwiftPM; para usar Periphery con sistemas de compilación que no sean de Apple, como Bazel, consulte Sistemas de compilación.
Después de responder algunas preguntas, Periphery imprimirá el comando de escaneo completo y lo ejecutará.
La configuración guiada solo está destinada a fines introductorios; una vez que esté familiarizado con Periphery, puede probar algunas opciones más avanzadas, todas las cuales se pueden ver con periphery help scan
.
Para obtener resultados coherentes de Periphery, es fundamental comprender las implicaciones de los objetivos de construcción que elija analizar. Por ejemplo, imagine un proyecto que consta de tres objetivos: aplicación, biblioteca y pruebas. El destino de la aplicación importa Lib y el destino de las pruebas importa tanto la aplicación como la Lib. Si proporcionara los tres a la opción --targets
, Periphery podrá analizar su proyecto en su conjunto. Sin embargo, si solo elige analizar aplicaciones y bibliotecas, pero no pruebas, Periphery puede informar algunas instancias de código no utilizado a las que solo hacen referencia las pruebas. Por lo tanto, cuando sospeche que Periphery ha proporcionado un resultado incorrecto, es importante considerar los objetivos que ha elegido analizar.
Si su proyecto consta de uno o más marcos independientes que no contienen también algún tipo de aplicación que consuma sus interfaces, entonces deberá decirle a Periphery que asuma que todas las declaraciones públicas se utilizan de hecho al incluir --retain-public
opción.
Para proyectos que combinan Objective-C y Swift, se recomienda encarecidamente leer sobre las implicaciones que esto puede tener en sus resultados.
Una vez que haya elegido las opciones adecuadas para su proyecto, es posible que desee conservarlas en un archivo de configuración YAML. La forma más sencilla de lograr esto es ejecutar Periphery con la opción --verbose
. Cerca del comienzo del resultado, verá la sección [configuration:begin]
con su configuración formateada como YAML a continuación. Copie y pegue la configuración en .periphery.yml
en la raíz de la carpeta de su proyecto. Ahora puede simplemente ejecutar periphery scan
y se utilizará la configuración YAML.
Periphery primero construye su proyecto. Para proyectos Xcode, los esquemas proporcionados a través de la opción --schemes
se crean usando xcodebuild
. Para los proyectos de Swift Package Manager, los objetivos individuales proporcionados mediante la opción --targets
se crean mediante swift build
. El compilador Swift emplea una técnica llamada índice durante la construcción para completar un almacén de índice que contiene información sobre la estructura del código fuente de su proyecto.
Una vez creado su proyecto, Periphery realiza una fase de indexación. Para cada archivo fuente que sea miembro de los objetivos proporcionados a través de la opción --targets
, Periphery obtiene su información estructural del almacén de índice y construye su propia representación gráfica interna de su proyecto. Periphery también analiza el árbol de sintaxis abstracta (AST) de cada archivo para completar algunos detalles que no proporciona el almacén de índice.
Una vez que se completa la indexación, Periphery analiza el gráfico para identificar el código no utilizado. Esta fase consta de una serie de pasos que modifican el gráfico para que sea más fácil identificar escenarios específicos de código no utilizado. El último paso recorre el gráfico desde sus raíces para identificar declaraciones a las que ya no se hace referencia.
El objetivo de Periphery es informar casos de declaraciones no utilizadas. Una declaración es una class
, struct
, protocol
, function
, property
, constructor
, enum
, typealias
, associatedtype
, etc. Como era de esperar, Periphery es capaz de identificar declaraciones simples sin referencia, por ejemplo, una class
que ya no se usa en ninguna parte de su base de código.
La periferia también puede identificar instancias más avanzadas de código no utilizado. La siguiente sección los explica en detalle.
La periferia puede identificar parámetros de función no utilizados. También se pueden identificar instancias de parámetros no utilizados en protocolos y sus declaraciones conformes, así como parámetros en métodos anulados. Ambos escenarios se explican más adelante.
Un parámetro no utilizado de una función de protocolo solo se informará como no utilizado si el parámetro tampoco se utiliza en todas las implementaciones.
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. " )
}
}
Consejo
Puede ignorar todos los parámetros no utilizados de los protocolos y funciones conformes con la opción
--retain-unused-protocol-func-params
.
De manera similar a los protocolos, los parámetros de las funciones anuladas solo se informan como no utilizados si tampoco se utilizan en la función base y en todas las funciones anuladas.
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. " )
}
}
Los parámetros no utilizados de protocolos o clases definidos en módulos externos (por ejemplo, Foundation) siempre se ignoran, ya que no tiene acceso para modificar la declaración de la función base.
Los parámetros no utilizados de funciones que simplemente llaman fatalError
también se ignoran. Estas funciones suelen ser inicializadores necesarios no implementados en subclases.
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 " )
}
}
Un protocolo conformado por un objeto no se usa realmente a menos que también se use como un tipo existencial o para especializar un método/clase genérico. La periferia es capaz de identificar dichos protocolos redundantes, ya sea que estén conformados por uno o incluso varios objetos.
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 ( )
Aquí podemos ver que a pesar de que se llaman ambas implementaciones de someMethod
, en ningún momento un objeto adopta el tipo MyProtocol
. Por lo tanto, el protocolo en sí es redundante y no hay ningún beneficio si MyClass1
o MyClass2
se ajustan a él. Podemos eliminar MyProtocol
junto con cada conformidad redundante y simplemente mantener someMethod
en cada clase.
Al igual que un método o propiedad normal de un objeto, las propiedades y métodos individuales declarados por su protocolo también pueden identificarse como no utilizados.
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 ( )
Aquí podemos ver que MyProtocol
se utiliza y no se puede eliminar. Sin embargo, dado que unusedProperty
nunca se llama en MyConformingClass
, Periphery puede identificar que la declaración de unusedProperty
en MyProtocol
tampoco se utiliza y se puede eliminar junto con la implementación no utilizada de unusedProperty
.
Además de poder identificar enumeraciones no utilizadas, Periphery también puede identificar casos de enumeraciones individuales no utilizados. Las enumeraciones simples que no son representables sin formato, es decir, que no tienen un tipo de valor String
, Character
, Int
o punto flotante, se pueden identificar de manera confiable. Sin embargo, las enumeraciones que tienen un tipo de valor sin formato pueden ser de naturaleza dinámica y, por lo tanto, se debe suponer que se utilizan.
Aclaremos esto con un ejemplo rápido:
enum MyEnum : String {
case myCase
}
func someFunction ( value : String ) {
if let myEnum = MyEnum ( rawValue : value ) {
somethingImportant ( myEnum )
}
}
No hay una referencia directa al caso myCase
, por lo que es razonable esperar que ya no sea necesario; sin embargo, si se eliminara, podemos ver que nunca se llamaría somethingImportant
si someFunction
se le pasara el valor de "myCase"
.
Las propiedades que se asignan pero nunca se utilizan se identifican como tales, por ejemplo:
class MyClass {
var assignOnlyProperty : String // 'assignOnlyProperty' is assigned, but never used
init ( value : String ) {
self . assignOnlyProperty = value
}
}
En algunos casos, este puede ser el comportamiento deseado; por lo tanto, tiene algunas opciones disponibles para silenciar dichos resultados:
--retain-assign-only-property-types
. Los tipos dados deben coincidir con su uso exacto en la declaración de propiedad (sin signo de interrogación opcional), por ejemplo, String
, [String]
, Set
. Periphery no puede resolver tipos de propiedades inferidos; por lo tanto, en algunos casos es posible que necesite agregar anotaciones de tipo explícitas a sus propiedades.--retain-assign-only-properties
. Las declaraciones que están marcadas como public
pero a las que no se hace referencia desde fuera de su módulo de inicio se identifican como de accesibilidad pública redundante. En este escenario, la anotación public
se puede eliminar de la declaración. Eliminar la accesibilidad pública redundante tiene un par de beneficios:
final
al descubrir automáticamente todas las declaraciones potencialmente anuladoras. Las clases final
están mejor optimizadas por el compilador. Este análisis se puede desactivar con --disable-redundant-public-analysis
.
Periphery puede detectar importaciones no utilizadas de objetivos que ha escaneado, es decir, aquellos especificados con el argumento --targets
. No puede detectar importaciones no utilizadas de otros destinos porque los archivos fuente de Swift no están disponibles y no se pueden observar los usos de @_exported
. @_exported
es problemático porque cambia la interfaz pública de un objetivo de modo que las declaraciones exportadas por el objetivo ya no son necesariamente declaradas por el objetivo importado. Por ejemplo, el objetivo Foundation
es exportar Dispatch
, entre otros objetivos. Si un archivo fuente determinado importa Foundation
y hace referencia DispatchQueue
pero no a otras declaraciones de Foundation
, entonces la importación Foundation
no se puede eliminar ya que también haría que el tipo DispatchQueue
no esté disponible. Por lo tanto, para evitar falsos positivos, Periphery solo detecta importaciones no utilizadas de objetivos que ha escaneado.
Es probable que Periphery produzca falsos positivos para objetivos con una combinación de Swift y Objective-C, ya que Periphery no puede escanear los archivos de Objective-C. Por lo tanto, se recomienda deshabilitar la detección de importaciones no utilizadas para proyectos con una cantidad significativa de Objective-C, o excluir manualmente los destinos de lenguaje mixto de los resultados.
Periphery no puede analizar el código Objective-C ya que los tipos se pueden escribir dinámicamente.
De forma predeterminada, Periphery no asume que las declaraciones accesibles mediante el tiempo de ejecución de Objective-C estén en uso. Si su proyecto es una combinación de Swift y Objective-C, puede habilitar este comportamiento con la opción --retain-objc-accessible
. Las declaraciones Swift a las que puede acceder el tiempo de ejecución de Objective-C son aquellas que están anotadas explícitamente con @objc
o @objcMembers
y las clases que heredan NSObject
directa o indirectamente a través de otra clase.
Alternativamente, --retain-objc-annotated
se puede usar para retener solo declaraciones que están explícitamente anotadas con @objc
o @objcMembers
. Los tipos que heredan NSObject
no se conservan a menos que tengan anotaciones explícitas. Esta opción puede descubrir más código no utilizado, pero con la advertencia de que algunos de los resultados pueden ser incorrectos si la declaración de hecho se usa en código Objective-C. Para resolver estos resultados incorrectos, debe agregar una anotación @objc
a la declaración.
Swift sintetiza código adicional para tipos Codable
que no es visible para Periphery y puede generar falsos positivos para propiedades a las que no se hace referencia directamente desde código no sintetizado. Si su proyecto contiene muchos de estos tipos, puede conservar todas las propiedades en los tipos Codable
con --retain-codable-properties
. Alternativamente, puede conservar propiedades solo en tipos Encodable
con --retain-encodable-properties
.
Si un protocolo en un módulo externo no escaneado por Periphery declara la conformidad Codable
, puede indicarle a Periphery que identifique los protocolos como Codable
con --external-codable-protocols "ExternalProtocol"
.
Cualquier clase que herede XCTestCase
se conserva automáticamente junto con sus métodos de prueba. Sin embargo, cuando una clase hereda XCTestCase
indirectamente a través de otra clase, por ejemplo, UnitTestCase
, y esa clase reside en un objetivo que no es escaneado por Periphery, necesita usar la opción --external-test-case-classes UnitTestCase
para indicarle a Periphery que trate UnitTestCase
como una subclase XCTestCase
.
Si su proyecto contiene archivos de Interface Builder (como guiones gráficos y XIB), Periphery los tendrá en cuenta al identificar declaraciones no utilizadas. Sin embargo, Periphery actualmente solo identifica clases no utilizadas. Esta limitación existe porque Periphery aún no analiza completamente los archivos de Interface Builder (consulte el problema n.º 212). Debido al principio de diseño de Periphery de evitar falsos positivos, se supone que si se hace referencia a una clase en un archivo de Interface Builder, se utilizan todos sus IBOutlets
e IBActions
, incluso si no lo son en realidad. Este enfoque se revisará para identificar con precisión IBActions
y IBOutlets
no utilizados una vez que Periphery obtenga la capacidad de analizar archivos de Interface Builder.
Por el motivo que sea, es posible que desee conservar algún código no utilizado. Los comandos de comentarios del código fuente se pueden utilizar para ignorar declaraciones específicas y excluirlas de los resultados.
Se puede colocar un comando para ignorar comentario directamente en la línea encima de cualquier declaración para ignorarla y todas las declaraciones descendientes:
// periphery:ignore
class MyClass { }
También puede ignorar parámetros de función específicos no utilizados:
// periphery:ignore:parameters unusedOne,unusedTwo
func someFunc ( used : String , unusedOne : String , unusedTwo : String ) {
print ( used )
}
El comando // periphery:ignore:all
se puede colocar en la parte superior del archivo fuente para ignorar todo el contenido del archivo. Tenga en cuenta que el comentario debe colocarse encima de cualquier código, incluidas las declaraciones de importación.
Los comandos de comentarios también admiten comentarios finales que siguen un guión para que pueda incluir una explicación en la misma línea:
// periphery:ignore - explanation of why this is necessary
class MyClass { }
Antes de configurar la integración de Xcode, le recomendamos encarecidamente que primero haga funcionar Periphery en una terminal, ya que utilizará exactamente el mismo comando a través de Xcode.
Seleccione su proyecto en el Navegador de proyectos y haga clic en el botón + en la parte inferior izquierda de la sección Objetivos. Seleccione Multiplataforma y elija Agregar . Pulsa Siguiente.
Periphery es un proyecto apasionante que requiere un gran esfuerzo para mantenerlo y desarrollarlo. Si encuentra útil Periphery, considere patrocinar a través de Patrocinadores de GitHub.
Un agradecimiento especial a los siguientes generosos patrocinadores:
SaGa Corp desarrolla tecnología única para los actores financieros y sus clientes.
Emerge Tools es un conjunto de productos revolucionarios diseñados para potenciar las aplicaciones móviles y los equipos que las crean.