如何让Xcode检测协议遵守者的loadOrder变量重复并抛出报错/警告?
实现Xcode检测协议遵守者的loadOrder重复并抛出警告/错误
可以通过两种主流方式实现这个需求,都是在编译阶段就能让Xcode抛出警告或错误,避免运行时才发现问题:
一、用SwiftLint自定义静态分析规则
SwiftLint是Xcode常用的代码静态检查工具,支持自定义规则,能直接在IDE里标记出重复的loadOrder。
步骤1:定义Loadable协议
先明确协议要求,确保所有需要排序的类都遵守这个协议:
protocol Loadable { static var loadOrder: Int { get } } // 默认实现,给不需要自定义顺序的类用 extension Loadable { static var loadOrder: Int { 9999 } }
步骤2:编写自定义SwiftLint规则
创建一个LoadOrderDuplicateRule.swift文件,实现规则逻辑:
import SwiftLintCore struct LoadOrderDuplicateRule: Rule { var configuration = SeverityConfiguration(.warning) static let description = RuleDescription( identifier: "load_order_duplicate", name: "Load Order Duplicate", description: "Detects duplicate non-default loadOrder values among Loadable protocol conformers.", kind: .lint, nonTriggeringExamples: [ Example("class A: Loadable { static let loadOrder = 1 }"), Example("class B: Loadable { }"), // 使用默认9999 Example("class C: Loadable { static let loadOrder = 2 }") ], triggeringExamples: [ Example("class A: Loadable { static let loadOrder = 1 }"), Example("class B: Loadable { static let loadOrder = 1 }") ] ) func validate(file: SwiftLintFile) -> [StyleViolation] { var orderMap = [Int: [String]]() // 筛选所有遵守Loadable协议的类型 let loadableConformances = file.protocolConformances.filter { $0.protocolName == "Loadable" } for conformance in loadableConformances { guard let typeName = conformance.typeName.name else { continue } // 查找当前类型中名为loadOrder的静态属性 let staticProperties = file.structureDictionary .children .compactMap { $0 as? [String: Any] } .filter { let kind = $0["key.kind"] as? String return kind == "source.lang.swift.decl.class" || kind == "source.lang.swift.decl.struct" } .filter { ($0["key.name"] as? String) == typeName } .flatMap { $0["key.children"] as? [[String: Any]] ?? [] } .filter { $0["key.kind"] as? String == "source.lang.swift.decl.var.static" && ($0["key.name"] as? String) == "loadOrder" } // 提取loadOrder的数值 for property in staticProperties { guard let valueNodes = property["key.children"] as? [[String: Any]], let constantNode = valueNodes.first(where: { $0["key.kind"] as? String == "source.lang.swift.expr.constant" }), let valueStr = constantNode["key.value"] as? String, let orderValue = Int(valueStr) else { continue } // 跳过默认值9999的重复检查 guard orderValue != 9999 else { continue } if orderMap[orderValue] == nil { orderMap[orderValue] = [typeName] } else { orderMap[orderValue]?.append(typeName) } } } // 生成违规提示 var violations = [StyleViolation]() for (order, types) in orderMap where types.count > 1 { let errorMsg = "Load order \(order) is duplicated in types: \(types.joined(separator: ", "))" for typeName in types { if let conformance = loadableConformances.first(where: { $0.typeName.name == typeName }), let location = conformance.location { violations.append(StyleViolation( ruleDescription: Self.description, severity: configuration.severity, location: location, reason: errorMsg )) } } } return violations } }
步骤3:配置SwiftLint启用规则
在项目根目录的.swiftlint.yml文件中添加自定义规则配置:
custom_rules: load_order_duplicate: path: ./Scripts/LoadOrderDuplicateRule.swift # 替换成你的规则文件路径 severity: warning # 可以改成error直接阻断编译
之后只要运行SwiftLint(或者把SwiftLint集成到Xcode的Build Phases),Xcode就会在有重复loadOrder的地方标记警告/错误。
二、用编译期脚本(基于SwiftSyntax)
如果不想依赖SwiftLint,可以用SwiftSyntax写一个脚本,在Xcode构建阶段执行,直接输出错误信息让Xcode识别。
步骤1:创建检查脚本
创建一个CheckLoadOrder.swift文件,用SwiftSyntax解析代码:
import Foundation import SwiftSyntax func checkLoadOrder(in directory: String) { let fileManager = FileManager.default var orderMap = [Int: [String]]() // 遍历所有.swift文件 let enumerator = fileManager.enumerator(atPath: directory)! for case let file as String in enumerator where file.hasSuffix(".swift") { let filePath = URL(fileURLWithPath: directory).appendingPathComponent(file).path let sourceFile = try! SyntaxParser.parse(filePath) // 遍历所有类型声明 sourceFile.walk { node -> SyntaxVisitorContinueKind in guard let typeDecl = node as? TypeDeclSyntaxProtocol else { return .visitChildren } let typeName = typeDecl.name.text // 检查是否遵守Loadable协议 let conformsToLoadable = typeDecl.inheritanceClause?.inheritedTypes.contains { $0.typeName.text == "Loadable" } ?? false guard conformsToLoadable else { return .visitChildren } // 查找loadOrder静态属性 if let classDecl = typeDecl as? ClassDeclSyntax { for member in classDecl.members { guard let varDecl = member.decl as? VariableDeclSyntax, varDecl.modifiers.contains(where: { $0.name.text == "static" }), let binding = varDecl.bindings.first, binding.pattern.text == "loadOrder", let initializer = binding.initializer, let integerLiteral = initializer.value as? IntegerLiteralExprSyntax, let orderValue = Int(integerLiteral.digits.text) else { continue } // 跳过默认值9999 guard orderValue != 9999 else { continue } if orderMap[orderValue] == nil { orderMap[orderValue] = [typeName] } else { orderMap[orderValue]?.append(typeName) } } } return .visitChildren } } // 输出错误信息 for (order, types) in orderMap where types.count > 1 { print("error: Load order \(order) is duplicated in types: \(types.joined(separator: ", "))") } } // 传入项目源码目录 checkLoadOrder(in: "./YourProjectSources") // 替换成你的源码路径
步骤2:集成到Xcode Build Phases
- 在Xcode项目中添加一个新的Run Script Build Phase
- 输入脚本命令:
swift ./Scripts/CheckLoadOrder.swift(替换成你的脚本路径) - 确保这个脚本在Compile Sources之前执行
这样每次构建时,脚本会自动检查,一旦发现重复的loadOrder,Xcode会在Issue Navigator里显示错误,甚至可以阻断编译。
补充说明
- 如果需要允许默认值9999重复,两种方案里都已经做了过滤;如果连默认值也不想重复,删掉判断
orderValue != 9999的逻辑即可。 - SwiftLint方案的优势是能在编写代码时实时提示,体验更好;脚本方案更轻量,不需要依赖第三方工具。
内容的提问来源于stack exchange,提问作者Tiny Tim
相关产品推荐
相关产品推荐

