brew install peripheryapp/periphery/periphery
mint install peripheryapp/periphery
scan
명령스캔 명령은 Periphery의 주요 기능입니다. 안내 설정을 시작하려면 프로젝트 디렉터리로 변경하고 다음을 실행하면 됩니다.
periphery scan --setup
안내 설정은 Xcode 및 SwiftPM 프로젝트에서만 작동합니다. Bazel과 같은 Apple 이외의 빌드 시스템에서 Periphery를 사용하려면 빌드 시스템을 참조하세요.
몇 가지 질문에 답한 후 Periphery는 전체 스캔 명령을 인쇄하고 실행합니다.
안내식 설정은 소개용으로만 제공됩니다. 주변 장치에 익숙해지면 몇 가지 고급 옵션을 시도해 볼 수 있으며 모든 옵션은 periphery help scan
에서 볼 수 있습니다.
Periphery에서 일관된 결과를 얻으려면 분석하기 위해 선택한 빌드 대상의 의미를 이해하는 것이 중요합니다. 예를 들어 App, Lib 및 Tests라는 세 가지 대상으로 구성된 프로젝트를 상상해 보세요. App 대상은 Lib을 가져오고, Tests 대상은 App과 Lib을 모두 가져옵니다. --targets
옵션에 세 가지를 모두 제공하면 Periphery가 프로젝트를 전체적으로 분석할 수 있습니다. 그러나 앱과 Lib만 분석하고 테스트는 분석하지 않기로 선택한 경우 Periphery는 테스트 에서만 참조하는 사용되지 않은 코드의 일부 인스턴스를 보고할 수 있습니다. 따라서 Periphery가 잘못된 결과를 제공했다고 의심되는 경우 분석하기 위해 선택한 대상을 고려하는 것이 중요합니다.
프로젝트가 인터페이스를 사용하는 일종의 애플리케이션을 포함하지 않는 하나 이상의 독립 실행형 프레임워크로 구성된 경우 --retain-public
포함하여 모든 공개 선언이 실제로 사용된다고 가정하도록 Periphery에 지시해야 합니다. 옵션.
Objective-C와 Swift가 혼합된 프로젝트의 경우 이것이 결과에 미칠 수 있는 영향에 대해 읽어 보는 것이 좋습니다.
프로젝트에 적합한 옵션을 결정한 후에는 해당 옵션을 YAML 구성 파일에 유지할 수 있습니다. 이를 달성하는 가장 간단한 방법은 --verbose
옵션을 사용하여 Periphery를 실행하는 것입니다. 출력 시작 부분에 아래 YAML 형식의 구성이 포함된 [configuration:begin]
섹션이 표시됩니다. 구성을 복사하여 프로젝트 폴더 루트의 .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
자체가 사용되며 제거할 수 없음을 알 수 있습니다. 그러나 unusedProperty
MyConformingClass
에서 호출되지 않으므로 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
). 주변 장치는 유추된 속성 유형을 확인할 수 없으므로 경우에 따라 속성에 명시적 유형 주석을 추가해야 할 수도 있습니다.--retain-assign-only-properties
사용하여 할당 전용 속성 분석을 완전히 비활성화합니다. public
로 표시되었지만 홈 모듈 외부에서 참조되지 않은 선언은 중복된 공개 액세스 가능성이 있는 것으로 식별됩니다. 이 시나리오에서는 public
주석을 선언에서 제거할 수 있습니다. 중복된 공개 접근성을 제거하면 다음과 같은 몇 가지 이점이 있습니다.
final
수행할 수 있습니다. final
클래스는 컴파일러에 의해 더 잘 최적화됩니다. --disable-redundant-public-analysis
으로 이 분석을 비활성화할 수 있습니다.
주변 장치는 스캔한 대상, 즉 --targets
인수로 지정된 대상의 사용되지 않은 가져오기를 감지할 수 있습니다. Swift 소스 파일을 사용할 수 없고 @_exported
사용을 관찰할 수 없기 때문에 다른 대상의 사용되지 않은 가져오기를 감지할 수 없습니다. @_exported
대상에서 내보낸 선언이 더 이상 가져온 대상에서 선언되지 않도록 대상의 공개 인터페이스를 변경하기 때문에 문제가 있습니다. 예를 들어 Foundation
대상은 다른 대상 중에서 Dispatch
내보냅니다. 특정 소스 파일이 Foundation
가져오고 DispatchQueue
참조하지만 Foundation
의 다른 선언은 없는 경우 DispatchQueue
유형을 사용할 수 없게 되므로 Foundation
가져오기를 제거할 수 없습니다. 따라서 오탐을 방지하기 위해 Periphery는 스캔한 대상 중 사용되지 않은 가져오기만 감지합니다.
Periphery는 Objective-C 파일을 스캔할 수 없으므로 Swift와 Objective-C가 혼합된 대상에 대해 오탐지를 생성할 가능성이 높습니다. 따라서 상당한 양의 Objective-C가 포함된 프로젝트에 대해 사용되지 않은 가져오기 감지를 비활성화하거나 결과에서 혼합 언어 대상을 수동으로 제외하는 것이 좋습니다.
유형이 동적으로 유형화될 수 있으므로 주변 장치에서는 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
상속하는 모든 클래스는 해당 테스트 메서드와 함께 자동으로 유지됩니다. 그러나 클래스가 다른 클래스(예: UnitTestCase
를 통해 XCTestCase
간접적으로 상속하고 해당 클래스가 Periphery에서 검사하지 않는 대상에 있는 경우 --external-test-case-classes UnitTestCase
옵션을 사용하여 Periphery에 다음을 지시해야 합니다. UnitTestCase
XCTestCase
하위 클래스로 처리합니다.
프로젝트에 인터페이스 빌더 파일(예: 스토리보드 및 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 통합을 설정하기 전에 Xcode를 통해 정확히 동일한 명령을 사용하게 되므로 먼저 터미널에서 Periphery를 작동시키는 것이 좋습니다.
프로젝트 네비게이터에서 프로젝트를 선택하고 대상 섹션 왼쪽 하단에 있는 + 버튼을 클릭합니다. 크로스 플랫폼을 선택하고 집계를 선택합니다. 다음을 누르세요.
Periphery는 유지하고 개발하는 데 엄청난 노력이 필요한 열정 프로젝트입니다. Periphery가 유용하다고 생각되면 GitHub 후원자를 통한 후원을 고려해 보세요.
다음의 관대한 후원자들에게 특별한 감사를 드립니다:
SaGa Corp는 금융 기관과 고객을 위한 고유한 기술을 개발합니다.
Emerge Tools는 모바일 앱과 이를 구축하는 팀을 강화하기 위해 설계된 혁신적인 제품 모음입니다.