brew install peripheryapp/periphery/periphery
mint install peripheryapp/periphery
scan
命令掃描指令是 Periphery 的主要功能。要開始引導設置,只需更改到您的專案目錄並運行:
periphery scan --setup
引導式設定僅適用於 Xcode 和 SwiftPM 項目,若要將 Periphery 與非 Apple 建置系統(例如 Bazel)一起使用,請參閱建置系統。
回答幾個問題後,Periphery 會列印出完整的掃描命令並執行。
引導設定僅用於介紹目的,一旦您熟悉了 Periphery,您可以嘗試一些更高級的選項,所有這些都可以透過periphery help scan
看到。
為了從 Periphery 獲得一致的結果,了解您選擇分析的構建目標的含義至關重要。例如,假設一個專案由三個目標組成:App、Lib 和 Tests。 App 目標匯入 Lib,Tests 目標同時匯入 App 和 Lib。如果您將這三個選項提供給--targets
選項,那麼 Periphery 將能夠從整體上分析您的專案。但是,如果您只選擇分析 App 和 Lib,而不分析測試,Periphery 可能會報告一些僅由測試引用的未使用程式碼的實例。因此,當您懷疑 Periphery 提供了不正確的結果時,考慮您選擇分析的目標非常重要。
如果您的專案由一個或多個獨立框架組成,這些框架也不包含某種使用其介面的應用程序,那麼您需要告訴 Periphery 假設所有公共聲明實際上都透過包含--retain-public
來使用選項。
對於混合使用 Objective-C 和 Swift 的項目,強烈建議您閱讀這可能對您的結果產生的影響。
一旦為您的專案確定了適當的選項,您可能會想要將它們保留在 YAML 設定檔中。實現此目的的最簡單方法是使用--verbose
選項來執行 Periphery。在輸出的開頭附近,您將看到[configuration:begin]
部分,其中的設定格式如下 YAML。將設定複製並貼上到專案資料夾根目錄中的.periphery.yml
中。現在您可以簡單地執行periphery scan
,並且將使用 YAML 設定。
Periphery 首先建立您的專案。對於 Xcode 項目,透過--schemes
選項提供的方案是使用xcodebuild
的。對於 Swift Package Manager 項目,透過--targets
選項提供的各個目標是使用swift build
的。 Swift 編譯器採用稱為「建置時索引」的技術來填入索引存儲,其中包含有關專案原始碼結構的資訊。
專案建置後,Periphery 會執行索引階段。對於作為透過--targets
選項提供的目標成員的每個源文件,Periphery 從索引存儲中獲取其結構信息,並構建其自己的項目的內部圖形表示。 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
本身已被使用,並且無法刪除。但是,由於MyConformingClass
上從未呼叫unusedProperty
,因此 Periphery 能夠識別出MyProtocol
中的unusedProperty
聲明也未使用,並且可以與unusedProperty
的未使用實作一起刪除。
除了能夠識別未使用的枚舉之外,Periphery 還可以識別單一未使用的枚舉情況。不能原始表示的普通枚舉,即沒有String
、 Character
、 Int
或浮點值類型的普通枚舉可以被可靠地識別。但是,確實具有原始值類型的枚舉本質上可以是動態的,因此必須假設已使用。
讓我們透過一個簡單的例子來澄清這一點:
enum MyEnum : String {
case myCase
}
func someFunction ( value : String ) {
if let myEnum = MyEnum ( rawValue : value ) {
somethingImportant ( myEnum )
}
}
沒有直接引用myCase
案例,因此可以合理地預期它可能不再需要,但是如果將其刪除,我們可以看到,如果someFunction
傳遞了"myCase"
的值,則永遠不會調用somethingImportant
。
已分配但從未使用的屬性被如此標識,例如:
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
。 Periphery 無法解析推斷的屬性類型,因此在某些情況下您可能需要在屬性中新增明確類型註解。--retain-assign-only-properties
完全停用僅分配屬性分析。標記為public
但未從其主模組外部引用的聲明被標識為具有冗餘的公共可訪問性。在這種情況下,可以從聲明中刪除public
註釋。消除多餘的公共可近性有幾個好處:
final
。 final
類別可以被編譯器更好地最佳化。可以使用--disable-redundant-public-analysis
停用此分析。
週邊設備可以偵測已掃描的目標的未使用導入,即使用--targets
參數指定的目標。它無法偵測其他目標未使用的導入,因為 Swift 原始檔不可用且無法觀察@_exported
的使用。 @_exported
是有問題的,因為它更改了目標的公共接口,使得目標導出的聲明不再一定由導入的目標聲明。例如, Foundation
目標匯出Dispatch
等目標。如果任何給定的來源檔案導入Foundation
並引用DispatchQueue
但沒有來自Foundation
的其他聲明,則無法刪除Foundation
導入,因為它也會使DispatchQueue
類型不可用。因此,為了避免誤報,Periphery 僅偵測已掃描目標的未使用導入。
Periphery 可能會對混合 Swift 和 Objective-C 的目標產生誤報,因為 Periphery 無法掃描 Objective-C 檔案。因此,建議對包含大量 Objective-C 的項目停用未使用的導入偵測,或手動從結果中排除混合語言目標。
Periphery 無法分析 Objective-C 程式碼,因為類型可能是動態類型的。
預設情況下,Periphery 不假定 Objective-C 運行時可存取的宣告正在使用中。如果您的專案混合了 Swift 和 Objective-C,您可以使用--retain-objc-accessible
選項啟用此行為。 Objective-C 執行時期可存取的 Swift 宣告是那些以@objc
或@objcMembers
明確註解的聲明,以及直接或透過另一個類別間接繼承NSObject
的類別。
或者, --retain-objc-annotated
可用於僅保留使用@objc
或@objcMembers
明確註解的宣告。繼承NSObject
的類型不會被保留,除非它們具有明確註解。此選項可能會發現更多未使用的程式碼,但需要注意的是,如果該聲明實際上在 Objective-C 程式碼中使用,則某些結果可能不正確。要解決這些不正確的結果,您必須在聲明中添加@objc
註釋。
Swift 為 Periphery 不可見的Codable
類型合成了額外的程式碼,並且可能導致未直接從非合成程式碼引用的屬性出現誤報。如果您的專案包含許多此類類型,您可以使用--retain-codable-properties
保留Codable
類型的所有屬性。或者,您可以使用--retain-encodable-properties
僅保留Encodable
類型的屬性。
如果Codable
一致性是由 Periphery 未掃描的外部模組中的協定聲明的,您可以使用--external-codable-protocols "ExternalProtocol"
指示 Periphery 將協定識別為Codable
。
任何繼承XCTestCase
的類別都會自動保留及其測試方法。但是,當一個類別透過另一個類別間接繼承XCTestCase
時,例如UnitTestCase
,並且該類別駐留在 Periphery 未掃描的目標中,您需要使用--external-test-case-classes UnitTestCase
選項來指示 Periphery將UnitTestCase
視為XCTestCase
子類別。
如果您的專案包含 Interface Builder 檔案(例如 Storyboard 和 XIB),Periphery 在識別未使用的聲明時將考慮這些檔案。然而,Periphery 目前僅識別未使用的類別。有此限制是因為 Periphery 尚未完全解析 Interface Builder 檔案(請參閱問題 #212)。由於 Periphery 避免誤報的設計原則,假設如果在 Interface Builder 文件中引用一個類,則將使用其所有IBOutlets
和IBActions
,即使它們實際上可能並非如此。一旦 Periphery 獲得解析 Interface Builder 檔案的能力,將修改此方法以準確識別未使用的IBActions
和IBOutlets
。
無論出於何種原因,您可能會想要保留一些未使用的程式碼。原始碼註釋命令可用於忽略特定聲明,並將其從結果中排除。
忽略註釋命令可以直接放置在任何聲明上方的行上以忽略它以及所有後代聲明:
// periphery:ignore
class MyClass { }
您也可以忽略特定未使用的函數參數:
// periphery:ignore:parameters unusedOne,unusedTwo
func someFunc ( used : String , unusedOne : String , unusedTwo : String ) {
print ( used )
}
// periphery:ignore:all
指令可以放置在來源檔案的頂部,以忽略檔案的全部內容。請注意,註解必須放置在任何程式碼之上,包括 import 語句。
註釋命令還支援連字符後的尾隨註釋,以便您可以在同一行中包含說明:
// periphery:ignore - explanation of why this is necessary
class MyClass { }
在設定 Xcode 整合之前,我們強烈建議您先在終端機中使用 Periphery,因為您將透過 Xcode 使用完全相同的命令。
在項目導覽器中選擇您的項目,然後按一下目標部分左下角的 + 按鈕。選擇跨平台並選擇聚合。點擊下一步。
Periphery 是一個充滿熱情的項目,需要付出巨大的努力來維護和開發。如果您發現 Periphery 有用,請考慮透過 GitHub Sponsors 進行贊助。
特別感謝以下慷慨贊助商:
SaGa Corp 為金融機構及其客戶開發獨特的技術。
Emerge Tools 是一套革命性的產品,旨在增強行動應用程式及其建置團隊的能力。