brew install peripheryapp/periphery/periphery
mint install peripheryapp/periphery
scan
O comando scan é a função principal do Periphery. Para iniciar uma configuração guiada, basta mudar para o diretório do seu projeto e executar:
periphery scan --setup
A configuração guiada funciona apenas para projetos Xcode e SwiftPM. Para usar o Periphery com sistemas de compilação que não são da Apple, como Bazel, consulte Build Systems.
Depois de responder a algumas perguntas, o Periphery imprimirá o comando de varredura completa e o executará.
A configuração guiada destina-se apenas a fins introdutórios. Depois de estar familiarizado com o Periphery, você pode tentar algumas opções mais avançadas, todas as quais podem ser vistas com a periphery help scan
.
Para obter resultados coerentes do Periphery, é crucial compreender as implicações dos alvos de construção que você escolhe analisar. Por exemplo, imagine um projeto composto por três alvos: App, Lib e Tests. O destino App importa Lib, e o destino Tests importa App e Lib. Se você fornecer todos os três para a opção --targets
, o Periphery poderá analisar seu projeto como um todo. No entanto, se você optar por analisar apenas App e Lib, mas não testes, o Periphery poderá relatar algumas instâncias de código não utilizado que são referenciadas apenas por testes. Portanto, quando você suspeitar que o Periphery forneceu um resultado incorreto, é importante considerar os alvos que você escolheu analisar.
Se o seu projeto consiste em um ou mais frameworks autônomos que também não contêm algum tipo de aplicativo que consome suas interfaces, então você precisará dizer ao Periphery para assumir que todas as declarações públicas são de fato usadas, incluindo o --retain-public
opção.
Para projetos que combinam Objective-C e Swift, é altamente recomendável que você leia sobre as implicações que isso pode ter em seus resultados.
Depois de definir as opções apropriadas para o seu projeto, você pode querer mantê-las em um arquivo de configuração YAML. A maneira mais simples de conseguir isso é executar o Periphery com a opção --verbose
. Perto do início da saída você verá a seção [configuration:begin]
com sua configuração formatada como YAML abaixo. Copie e cole a configuração em .periphery.yml
na raiz da pasta do seu projeto. Agora você pode simplesmente executar periphery scan
e a configuração YAML será usada.
A periferia primeiro constrói seu projeto. Para projetos Xcode, os esquemas fornecidos pela opção --schemes
são construídos usando xcodebuild
. Para projetos do Swift Package Manager, os destinos individuais fornecidos por meio da opção --targets
são criados usando swift build
. O compilador Swift emprega uma técnica chamada index-while-building para preencher um armazenamento de índice que contém informações sobre a estrutura do código-fonte do seu projeto.
Após a construção do seu projeto, o Periphery realiza uma fase de indexação. Para cada arquivo de origem que é membro dos destinos fornecidos por meio da opção --targets
, o Periphery obtém suas informações estruturais do armazenamento de índice e constrói sua própria representação gráfica interna do seu projeto. O Periphery também analisa a árvore de sintaxe abstrata (AST) de cada arquivo para preencher alguns detalhes não fornecidos pelo armazenamento de índice.
Assim que a indexação for concluída, o Periphery analisa o gráfico para identificar o código não utilizado. Esta fase consiste em uma série de etapas que modificam o gráfico para facilitar a identificação de cenários específicos de código não utilizado. A etapa final percorre o gráfico desde suas raízes para identificar declarações que não são mais referenciadas.
O objetivo do Periphery é relatar instâncias de declarações não utilizadas. Uma declaração é class
, struct
, protocol
, function
, property
, constructor
, enum
, typealias
, associatedtype
, etc. Como seria de esperar, o Periphery é capaz de identificar declarações simples não referenciadas, por exemplo, uma class
que não é mais usada em nenhum lugar do seu base de código.
A periferia também pode identificar instâncias mais avançadas de código não utilizado. A seção a seguir explica isso em detalhes.
A periferia pode identificar parâmetros de função não utilizados. Instâncias de parâmetros não utilizados também podem ser identificadas em protocolos e suas declarações conformes, bem como parâmetros em métodos substituídos. Ambos os cenários são explicados mais abaixo.
Um parâmetro não utilizado de uma função de protocolo só será relatado como não utilizado se o parâmetro também não for utilizado em todas as implementações.
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. " )
}
}
Dica
Você pode ignorar todos os parâmetros não utilizados de protocolos e funções em conformidade com a opção
--retain-unused-protocol-func-params
.
Semelhante aos protocolos, os parâmetros de funções substituídas só serão relatados como não utilizados se também não forem utilizados na função base e em todas as funções de substituição.
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. " )
}
}
Parâmetros não utilizados de protocolos ou classes definidas em módulos externos (por exemplo, Foundation) são sempre ignorados, pois você não tem acesso para modificar a declaração da função base.
Parâmetros não utilizados de funções que simplesmente chamam fatalError
também são ignorados. Essas funções geralmente são inicializadores obrigatórios não implementados em 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 ) {
fatalError ( " init(param:) has not been implemented " )
}
}
Um protocolo conformado por um objeto não é verdadeiramente usado, a menos que também seja usado como um tipo existencial ou para especializar um método/classe genérico. A periferia é capaz de identificar esses protocolos redundantes, sejam eles compatíveis com um ou mesmo vários 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 ( )
Aqui podemos ver que apesar de ambas as implementações de someMethod
serem chamadas, em nenhum momento um objeto assume o tipo de MyProtocol
. Portanto, o protocolo em si é redundante e não há benefício em MyClass1
ou MyClass2
estar em conformidade com ele. Podemos remover MyProtocol
junto com cada conformidade redundante e apenas manter someMethod
em cada classe.
Assim como um método ou propriedade normal de um objeto, propriedades e métodos individuais declarados pelo seu protocolo também podem ser identificados como não 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 ( )
Aqui podemos ver que o próprio MyProtocol
é usado e não pode ser removido. No entanto, como unusedProperty
nunca é chamado em MyConformingClass
, o Periphery é capaz de identificar que a declaração de unusedProperty
em MyProtocol
também não é utilizada e pode ser removida junto com a implementação não utilizada de unusedProperty
.
Além de ser capaz de identificar enumerações não utilizadas, o Periphery também pode identificar casos individuais de enumerações não utilizadas. Enums simples que não são representáveis brutos, ou seja, que não possuem um tipo de valor String
, Character
, Int
ou ponto flutuante podem ser identificados de forma confiável. No entanto, enumerações que possuem um tipo de valor bruto podem ser de natureza dinâmica e, portanto, devem ser consideradas para serem usadas.
Vamos esclarecer isso com um exemplo rápido:
enum MyEnum : String {
case myCase
}
func someFunction ( value : String ) {
if let myEnum = MyEnum ( rawValue : value ) {
somethingImportant ( myEnum )
}
}
Não há referência direta ao caso myCase
, então é razoável esperar que ele não seja mais necessário, no entanto, se ele for removido, podemos ver que somethingImportant
nunca seria chamado se someFunction
recebesse o valor de "myCase"
.
As propriedades atribuídas, mas nunca utilizadas, são identificadas como tal, por exemplo:
class MyClass {
var assignOnlyProperty : String // 'assignOnlyProperty' is assigned, but never used
init ( value : String ) {
self . assignOnlyProperty = value
}
}
Em alguns casos este pode ser o comportamento pretendido, portanto você tem algumas opções disponíveis para silenciar tais resultados:
--retain-assign-only-property-types
. Os tipos fornecidos devem corresponder exatamente ao seu uso na declaração de propriedade (sem ponto de interrogação opcional), por exemplo, String
, [String]
, Set
. O Periphery não consegue resolver tipos de propriedade inferidos; portanto, em alguns casos, pode ser necessário adicionar anotações de tipo explícitas às suas propriedades.--retain-assign-only-properties
. As declarações marcadas como public
mas que não são referenciadas fora do seu módulo inicial, são identificadas como tendo acessibilidade pública redundante. Neste cenário, a anotação public
pode ser removida da declaração. A remoção da acessibilidade pública redundante traz alguns benefícios:
final
descobrindo automaticamente todas as declarações potencialmente substituídas. as classes final
são melhor otimizadas pelo compilador. Esta análise pode ser desabilitada com --disable-redundant-public-analysis
.
O Periphery pode detectar importações não utilizadas de alvos que varreu, ou seja, aqueles especificados com o argumento --targets
. Ele não pode detectar importações não utilizadas de outros destinos porque os arquivos de origem Swift não estão disponíveis e os usos de @_exported
não podem ser observados. @_exported
é problemático porque altera a interface pública de um destino de forma que as declarações exportadas pelo destino não sejam mais necessariamente declaradas pelo destino importado. Por exemplo, o alvo Foundation
exporta Dispatch
, entre outros alvos. Se algum arquivo de origem importar Foundation
e fizer referência DispatchQueue
, mas nenhuma outra declaração de Foundation
, a importação Foundation
não poderá ser removida, pois também tornaria o tipo DispatchQueue
indisponível. Para evitar falsos positivos, portanto, o Periphery detecta apenas importações não utilizadas de alvos que verificou.
O Periphery provavelmente produzirá falsos positivos para alvos com Swift e Objective-C mistos, já que o Periphery não pode verificar os arquivos Objective-C. Recomenda-se, portanto, desativar a detecção de importação não utilizada para projetos com uma quantidade significativa de Objective-C ou excluir manualmente os destinos de idiomas mistos dos resultados.
A periferia não pode analisar o código Objective-C, pois os tipos podem ser digitados dinamicamente.
Por padrão, o Periphery não assume que as declarações acessíveis pelo tempo de execução do Objective-C estejam em uso. Se o seu projeto for uma mistura de Swift e Objective-C, você pode ativar esse comportamento com a opção --retain-objc-accessible
. As declarações Swift acessíveis pelo tempo de execução do Objective-C são aquelas anotadas explicitamente com @objc
ou @objcMembers
e classes que herdam NSObject
direta ou indiretamente por meio de outra classe.
Alternativamente, --retain-objc-annotated
pode ser usado para reter apenas declarações explicitamente anotadas com @objc
ou @objcMembers
. Os tipos que herdam NSObject
não são retidos, a menos que tenham anotações explícitas. Esta opção pode revelar mais código não utilizado, mas com a ressalva de que alguns dos resultados podem estar incorretos se a declaração for de fato usada no código Objective-C. Para resolver esses resultados incorretos você deve adicionar uma anotação @objc
à declaração.
Swift sintetiza código adicional para tipos Codable
que não são visíveis para Periphery e pode resultar em falsos positivos para propriedades não referenciadas diretamente de código não sintetizado. Se o seu projeto contiver muitos desses tipos, você poderá reter todas as propriedades dos tipos Codable
com --retain-codable-properties
. Como alternativa, você pode reter propriedades apenas em tipos Encodable
com --retain-encodable-properties
.
Se a conformidade Codable
for declarada por um protocolo em um módulo externo não verificado pelo Periphery, você poderá instruir o Periphery a identificar os protocolos como Codable
com --external-codable-protocols "ExternalProtocol"
.
Qualquer classe que herde XCTestCase
é automaticamente retida junto com seus métodos de teste. No entanto, quando uma classe herda XCTestCase
indiretamente por meio de outra classe, por exemplo, UnitTestCase
, e essa classe reside em um destino que não é verificado pelo Periphery, você precisa usar a opção --external-test-case-classes UnitTestCase
para instruir o Periphery a trate UnitTestCase
como uma subclasse XCTestCase
.
Se o seu projeto contiver arquivos do Interface Builder (como storyboards e XIBs), o Periphery os levará em consideração ao identificar declarações não utilizadas. No entanto, o Periphery atualmente identifica apenas classes não utilizadas. Essa limitação existe porque o Periphery ainda não analisa completamente os arquivos do Interface Builder (consulte o problema nº 212). Devido ao princípio de design do Periphery de evitar falsos positivos, presume-se que se uma classe for referenciada em um arquivo do Interface Builder, todos os seus IBOutlets
e IBActions
serão usados, mesmo que não sejam na realidade. Esta abordagem será revisada para identificar com precisão IBActions
e IBOutlets
não utilizados assim que o Periphery ganhar a capacidade de analisar arquivos do Interface Builder.
Por alguma razão, você pode querer manter algum código não utilizado. Os comandos de comentário do código-fonte podem ser usados para ignorar declarações específicas e excluí-las dos resultados.
Um comando ignorar comentário pode ser colocado diretamente na linha acima de qualquer declaração para ignorá-la e todas as declarações descendentes:
// periphery:ignore
class MyClass { }
Você também pode ignorar parâmetros de função específicos não utilizados:
// periphery:ignore:parameters unusedOne,unusedTwo
func someFunc ( used : String , unusedOne : String , unusedTwo : String ) {
print ( used )
}
O comando // periphery:ignore:all
pode ser colocado no topo do arquivo de origem para ignorar todo o conteúdo do arquivo. Observe que o comentário deve ser colocado acima de qualquer código, incluindo instruções de importação.
Os comandos de comentário também suportam comentários finais após um hífen para que você possa incluir uma explicação na mesma linha:
// periphery:ignore - explanation of why this is necessary
class MyClass { }
Antes de configurar a integração do Xcode, é altamente recomendável que você primeiro faça o Periphery funcionar em um terminal, pois usará exatamente o mesmo comando via Xcode.
Selecione seu projeto no Project Navigator e clique no botão + no canto inferior esquerdo da seção Targets. Selecione Plataforma cruzada e escolha Agregar . Clique em Próximo.
Periferia é um projeto apaixonante que exige muito esforço para ser mantido e desenvolvido. Se você achar o Periphery útil, considere patrocinar por meio dos patrocinadores do GitHub.
Agradecimentos especiais vão para os seguintes patrocinadores generosos:
A SaGa Corp desenvolve tecnologia exclusiva para players financeiros e seus clientes.
Emerge Tools é um conjunto de produtos revolucionários projetados para turbinar os aplicativos móveis e as equipes que os criam.