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 是一套革命性的产品,旨在增强移动应用程序及其构建团队的能力。