brew install peripheryapp/periphery/periphery
mint install peripheryapp/periphery
scan
BefehlDer Scan-Befehl ist die Hauptfunktion der Peripherie. Um mit der geführten Einrichtung zu beginnen, wechseln Sie einfach in Ihr Projektverzeichnis und führen Sie Folgendes aus:
periphery scan --setup
Die geführte Einrichtung funktioniert nur für Xcode- und SwiftPM-Projekte. Zur Verwendung von Peripherie mit Nicht-Apple-Build-Systemen wie Bazel siehe Build-Systeme.
Nach Beantwortung einiger Fragen druckt Periphery den vollständigen Scanbefehl aus und führt ihn aus.
Die geführte Einrichtung ist nur für Einführungszwecke gedacht. Sobald Sie mit Periphery vertraut sind, können Sie einige erweiterte Optionen ausprobieren, die alle mit periphery help scan
angezeigt werden können.
Um kohärente Ergebnisse von Periphery zu erhalten, ist es wichtig, die Auswirkungen der Build-Ziele zu verstehen, die Sie analysieren möchten. Stellen Sie sich zum Beispiel ein Projekt vor, das aus drei Zielen besteht: App, Lib und Tests. Das App-Ziel importiert Lib und die Tests-Ziele importieren sowohl App als auch Lib. Wenn Sie alle drei für die Option --targets
angeben, kann Periphery Ihr Projekt als Ganzes analysieren. Wenn Sie sich jedoch dafür entscheiden, nur App und Lib, nicht aber Tests zu analysieren, meldet Periphery möglicherweise einige Instanzen nicht verwendeten Codes, auf die nur von Tests verwiesen wird. Wenn Sie daher vermuten, dass die Peripherie ein falsches Ergebnis geliefert hat, ist es wichtig, die Ziele zu berücksichtigen, die Sie zur Analyse ausgewählt haben.
Wenn Ihr Projekt aus einem oder mehreren eigenständigen Frameworks besteht, die nicht auch irgendeine Art von Anwendung enthalten, die ihre Schnittstellen nutzt, müssen Sie Periphery anweisen, davon auszugehen, dass alle öffentlichen Deklarationen tatsächlich verwendet werden, indem Sie --retain-public
einschließen Option.
Bei Projekten, die Objective-C und Swift kombinieren, wird dringend empfohlen, sich über die Auswirkungen zu informieren, die dies auf Ihre Ergebnisse haben kann.
Sobald Sie sich für die geeigneten Optionen für Ihr Projekt entschieden haben, möchten Sie diese möglicherweise in einer YAML-Konfigurationsdatei beibehalten. Der einfachste Weg, dies zu erreichen, besteht darin, Periphery mit der Option --verbose
auszuführen. Am Anfang der Ausgabe sehen Sie unten den Abschnitt [configuration:begin]
mit Ihrer Konfiguration im YAML-Format. Kopieren Sie die Konfiguration und fügen Sie sie in .periphery.yml
im Stammverzeichnis Ihres Projektordners ein. Sie können jetzt einfach periphery scan
ausführen und die YAML-Konfiguration wird verwendet.
Periphery erstellt zunächst Ihr Projekt. Für Xcode-Projekte werden die über die Option --schemes
bereitgestellten Schemata mit xcodebuild
erstellt. Bei Swift Package Manager-Projekten werden die einzelnen Ziele, die über die Option --targets
bereitgestellt werden, mit swift build
erstellt. Der Swift-Compiler verwendet eine Technik namens Index-while-Building, um einen Indexspeicher zu füllen, der Informationen über die Struktur des Quellcodes Ihres Projekts enthält.
Nachdem Ihr Projekt erstellt wurde, führt Periphery eine Indizierungsphase durch. Für jede Quelldatei, die Mitglied der über die Option --targets
bereitgestellten Ziele ist, ruft Periphery ihre Strukturinformationen aus dem Indexspeicher ab und erstellt eine eigene interne Diagrammdarstellung Ihres Projekts. Periphery analysiert außerdem den abstrakten Syntaxbaum (AST) jeder Datei, um einige Details zu ergänzen, die nicht vom Indexspeicher bereitgestellt werden.
Sobald die Indizierung abgeschlossen ist, analysiert Periphery das Diagramm, um nicht verwendeten Code zu identifizieren. Diese Phase besteht aus einer Reihe von Schritten, die das Diagramm verändern, um die Identifizierung bestimmter Szenarios ungenutzten Codes zu erleichtern. Im letzten Schritt wird das Diagramm von seinen Wurzeln aus durchsucht, um Deklarationen zu identifizieren, auf die nicht mehr verwiesen wird.
Das Ziel von Periphery besteht darin, Instanzen ungenutzter Deklarationen zu melden. Eine Deklaration ist eine class
, struct
, protocol
, function
, property
, constructor
, enum
, typealias
, associatedtype
usw. Wie erwartet ist Periphery in der Lage, einfache, nicht referenzierte Deklarationen zu identifizieren, z. B. eine class
, die nirgendwo mehr in Ihrem System verwendet wird Codebasis.
Die Peripherie kann auch fortgeschrittenere Instanzen ungenutzten Codes identifizieren. Im folgenden Abschnitt werden diese ausführlich erläutert.
Die Peripherie kann ungenutzte Funktionsparameter identifizieren. Instanzen nicht verwendeter Parameter können auch in Protokollen und ihren konformen Deklarationen sowie Parameter in überschriebenen Methoden identifiziert werden. Beide Szenarien werden weiter unten erläutert.
Ein unbenutzter Parameter einer Protokollfunktion wird nur dann als unbenutzt gemeldet, wenn der Parameter auch in allen Implementierungen unbenutzt ist.
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. " )
}
}
Tipp
Sie können alle nicht verwendeten Parameter von Protokollen und konformen Funktionen mit der Option
--retain-unused-protocol-func-params
ignorieren.
Ähnlich wie bei Protokollen werden Parameter überschriebener Funktionen nur dann als nicht verwendet gemeldet, wenn sie auch in der Basisfunktion und allen überschreibenden Funktionen nicht verwendet werden.
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. " )
}
}
Nicht verwendete Parameter von Protokollen oder Klassen, die in Fremdmodulen (z. B. Foundation) definiert sind, werden immer ignoriert, da Sie keinen Zugriff haben, um die Deklaration der Basisfunktion zu ändern.
Nicht verwendete Parameter von Funktionen, die einfach fatalError
aufrufen, werden ebenfalls ignoriert. Bei solchen Funktionen handelt es sich häufig um nicht implementierte erforderliche Initialisierer in Unterklassen.
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 " )
}
}
Ein Protokoll, dem ein Objekt entspricht, wird nicht wirklich verwendet, es sei denn, es wird auch als existenzieller Typ oder zur Spezialisierung einer generischen Methode/Klasse verwendet. Die Peripherie ist in der Lage, solche redundanten Protokolle zu identifizieren, unabhängig davon, ob sie von einem oder sogar mehreren Objekten eingehalten werden.
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 ( )
Hier können wir sehen, dass ein Objekt zu keinem Zeitpunkt den Typ MyProtocol
annimmt, obwohl beide Implementierungen von someMethod
aufgerufen werden. Daher ist das Protokoll selbst redundant und es bietet keinen Vorteil, wenn MyClass1
oder MyClass2
damit konform sind. Wir können MyProtocol
zusammen mit jeder redundanten Konformität entfernen und einfach someMethod
in jeder Klasse behalten.
Ebenso wie eine normale Methode oder Eigenschaft eines Objekts können auch einzelne von Ihrem Protokoll deklarierte Eigenschaften und Methoden als ungenutzt gekennzeichnet werden.
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 ( )
Hier können wir sehen, dass MyProtocol
selbst verwendet wird und nicht entfernt werden kann. Da jedoch unusedProperty
niemals für MyConformingClass
aufgerufen wird, kann Periphery erkennen, dass die Deklaration von unusedProperty
in MyProtocol
ebenfalls ungenutzt ist und zusammen mit der ungenutzten Implementierung von unusedProperty
entfernt werden kann.
Periphery ist nicht nur in der Lage, ungenutzte Aufzählungen zu identifizieren, sondern kann auch einzelne ungenutzte Aufzählungsfälle identifizieren. Einfache Aufzählungen, die nicht roh darstellbar sind, also keinen String
, Character
, Int
oder Gleitkommawerttyp haben, können zuverlässig identifiziert werden. Allerdings können Aufzählungen, die über einen Rohwerttyp verfügen , dynamischer Natur sein und müssen daher davon ausgegangen werden, dass sie verwendet werden.
Lassen Sie uns dies anhand eines kurzen Beispiels verdeutlichen:
enum MyEnum : String {
case myCase
}
func someFunction ( value : String ) {
if let myEnum = MyEnum ( rawValue : value ) {
somethingImportant ( myEnum )
}
}
Es gibt keinen direkten Verweis auf den myCase
-Fall, daher ist davon auszugehen, dass er möglicherweise nicht mehr benötigt wird. Wenn er jedoch entfernt würde, können wir sehen, dass somethingImportant
niemals aufgerufen würde, wenn someFunction
der Wert von "myCase"
übergeben würde.
Eigenschaften, die zugewiesen, aber nie genutzt werden, werden als solche gekennzeichnet, z. B.:
class MyClass {
var assignOnlyProperty : String // 'assignOnlyProperty' is assigned, but never used
init ( value : String ) {
self . assignOnlyProperty = value
}
}
In einigen Fällen kann dies das beabsichtigte Verhalten sein. Daher stehen Ihnen einige Optionen zur Verfügung, um solche Ergebnisse zu unterdrücken:
--retain-assign-only-property-types
bei. Angegebene Typen müssen ihrer genauen Verwendung in der Eigenschaftsdeklaration entsprechen (ohne optionales Fragezeichen), z. B. String
, [String]
, Set
. Periphery ist nicht in der Lage, abgeleitete Eigenschaftstypen aufzulösen. Daher müssen Sie in einigen Fällen möglicherweise explizite Typanmerkungen zu Ihren Eigenschaften hinzufügen.--retain-assign-only-properties
. Deklarationen, die public
gekennzeichnet sind, auf die jedoch nicht von außerhalb ihres Heimatmoduls verwiesen wird, werden als redundante öffentliche Zugänglichkeit gekennzeichnet. In diesem Szenario kann die public
Anmerkung aus der Deklaration entfernt werden. Das Entfernen redundanter öffentlicher Zugänglichkeit hat mehrere Vorteile:
final
schließen, indem es automatisch alle potenziell überschreibenden Deklarationen erkennt. final
werden vom Compiler besser optimiert. Diese Analyse kann mit --disable-redundant-public-analysis
deaktiviert werden.
Die Peripherie kann ungenutzte Importe von Zielen erkennen, die sie gescannt hat, also solche, die mit dem Argument --targets
angegeben wurden. Nicht verwendete Importe anderer Ziele können nicht erkannt werden, da die Swift-Quelldateien nicht verfügbar sind und die Verwendung von @_exported
nicht beobachtet werden kann. @_exported
ist problematisch, da es die öffentliche Schnittstelle eines Ziels so ändert, dass die vom Ziel exportierten Deklarationen nicht mehr unbedingt vom importierten Ziel deklariert werden. Beispielsweise exportiert das Foundation
Ziel unter anderem Dispatch
. Wenn eine bestimmte Quelldatei Foundation
importiert und auf DispatchQueue
verweist, aber keine anderen Deklarationen von Foundation
, kann der Foundation
Import nicht entfernt werden, da dadurch auch der DispatchQueue
-Typ nicht verfügbar wäre. Um Fehlalarme zu vermeiden, erkennt Periphery daher nur ungenutzte Importe von Zielen, die es gescannt hat.
Periphery wird wahrscheinlich falsch positive Ergebnisse für Ziele mit gemischtem Swift und Objective-C erzeugen, da Periphery die Objective-C-Dateien nicht scannen kann. Es wird daher empfohlen, die Erkennung ungenutzter Importe für Projekte mit einem erheblichen Anteil an Objective-C zu deaktivieren oder die gemischtsprachigen Ziele manuell aus den Ergebnissen auszuschließen.
Die Peripherie kann Objective-C-Code nicht analysieren, da Typen möglicherweise dynamisch typisiert werden.
Standardmäßig geht Periphery nicht davon aus, dass Deklarationen, auf die die Objective-C-Laufzeit zugreifen kann, verwendet werden. Wenn Ihr Projekt eine Mischung aus Swift und Objective-C ist, können Sie dieses Verhalten mit der Option --retain-objc-accessible
aktivieren. Swift-Deklarationen, auf die die Objective-C-Laufzeit zugreifen kann, sind diejenigen, die explizit mit @objc
oder @objcMembers
annotiert sind, und Klassen, die NSObject
entweder direkt oder indirekt über eine andere Klasse erben.
Alternativ kann --retain-objc-annotated
verwendet werden, um nur Deklarationen beizubehalten, die explizit mit @objc
oder @objcMembers
annotiert sind. Typen, die NSObject
erben, werden nicht beibehalten, es sei denn, sie verfügen über die expliziten Anmerkungen. Diese Option deckt möglicherweise mehr ungenutzten Code auf, allerdings mit der Einschränkung, dass einige der Ergebnisse möglicherweise falsch sind, wenn die Deklaration tatsächlich in Objective-C-Code verwendet wird. Um diese falschen Ergebnisse zu beheben, müssen Sie der Deklaration eine @objc
-Annotation hinzufügen.
Swift synthetisiert zusätzlichen Code für Codable
-Typen, der für Periphery nicht sichtbar ist, und kann zu Fehlalarmen für Eigenschaften führen, auf die nicht direkt von nicht synthetisiertem Code verwiesen wird. Wenn Ihr Projekt viele solcher Typen enthält, können Sie alle Eigenschaften von Codable
-Typen mit --retain-codable-properties
beibehalten. Alternativ können Sie Eigenschaften nur für Encodable
-Typen mit --retain-encodable-properties
beibehalten.
Wenn Codable
Konformität von einem Protokoll in einem externen Modul deklariert wird, das nicht von Periphery gescannt wird, können Sie Periphery anweisen, die Protokolle mit --external-codable-protocols "ExternalProtocol"
als Codable
zu identifizieren.
Jede Klasse, die XCTestCase
erbt, wird zusammen mit ihren Testmethoden automatisch beibehalten. Wenn eine Klasse XCTestCase
jedoch indirekt über eine andere Klasse erbt, z. B. UnitTestCase
, und sich diese Klasse in einem Ziel befindet, das nicht von Periphery gescannt wird, müssen Sie die --external-test-case-classes UnitTestCase
verwenden, um Periphery anzuweisen, dies zu tun Behandeln Sie UnitTestCase
als XCTestCase
Unterklasse.
Wenn Ihr Projekt Interface Builder-Dateien (z. B. Storyboards und XIBs) enthält, berücksichtigt Periphery diese bei der Identifizierung nicht verwendeter Deklarationen. Allerdings identifiziert Periphery derzeit nur ungenutzte Klassen. Diese Einschränkung besteht, weil Periphery Interface Builder-Dateien noch nicht vollständig analysiert (siehe Problem Nr. 212). Aufgrund des Designprinzips von Periphery, Fehlalarme zu vermeiden, wird davon ausgegangen, dass, wenn auf eine Klasse in einer Interface Builder-Datei verwiesen wird, alle ihre IBOutlets
und IBActions
verwendet werden, auch wenn dies in der Realität möglicherweise nicht der Fall ist. Dieser Ansatz wird überarbeitet, um ungenutzte IBActions
und IBOutlets
genau zu identifizieren, sobald Periphery die Fähigkeit erhält, Interface Builder-Dateien zu analysieren.
Aus irgendeinem Grund möchten Sie möglicherweise ungenutzten Code behalten. Quellcode-Kommentarbefehle können verwendet werden, um bestimmte Deklarationen zu ignorieren und sie aus den Ergebnissen auszuschließen.
Ein Befehl zum Ignorieren von Kommentaren kann direkt in der Zeile über jeder Deklaration platziert werden, um diese und alle untergeordneten Deklarationen zu ignorieren:
// periphery:ignore
class MyClass { }
Sie können auch bestimmte nicht verwendete Funktionsparameter ignorieren:
// periphery:ignore:parameters unusedOne,unusedTwo
func someFunc ( used : String , unusedOne : String , unusedTwo : String ) {
print ( used )
}
Der Befehl // periphery:ignore:all
kann oben in der Quelldatei platziert werden, um den gesamten Inhalt der Datei zu ignorieren. Beachten Sie, dass der Kommentar über jedem Code, einschließlich Importanweisungen, platziert werden muss.
Kommentarbefehle unterstützen auch nachgestellte Kommentare nach einem Bindestrich, sodass Sie in derselben Zeile eine Erklärung einfügen können:
// periphery:ignore - explanation of why this is necessary
class MyClass { }
Bevor Sie die Xcode-Integration einrichten, empfehlen wir Ihnen dringend, Periphery zunächst in einem Terminal zum Laufen zu bringen, da Sie über Xcode genau denselben Befehl verwenden.
Wählen Sie Ihr Projekt im Projektnavigator aus und klicken Sie unten links im Abschnitt „Ziele“ auf die Schaltfläche „+“. Wählen Sie „Plattformübergreifend“ und dann „Aggregate“ aus. Klicken Sie auf Weiter.
Periphery ist ein Leidenschaftsprojekt, dessen Wartung und Entwicklung einen enormen Aufwand erfordert. Wenn Sie Periphery nützlich finden, denken Sie bitte darüber nach, über GitHub Sponsors zu sponsern.
Besonderer Dank geht an folgende großzügige Sponsoren:
SaGa Corp entwickelt einzigartige Technologie für Finanzakteure und ihre Kunden.
Emerge Tools ist eine Suite revolutionärer Produkte, die darauf ausgelegt sind, mobile Apps und die Teams, die sie entwickeln, zu optimieren.