Sourcery与协议定向编程:自动生成协议实现代码
在Swift开发中,协议定向编程(Protocol-Oriented Programming)是构建灵活、可扩展系统的核心范式。然而,手动实现协议方法往往导致大量重复代码,降低开发效率并增加出错风险。Sourcery作为Swift元编程工具,通过模板驱动的代码生成,彻底解决了这一痛点。本文将深入探讨如何利用Sourcery实现协议方法的自动化生成,提升代码质量与开发效率。
协议实现的痛点与Sourcery解决方案
传统协议实现的困境
当一个项目中存在多个遵循同一协议的类型时,开发者需要为每个类型编写相同的协议方法实现。例如,为所有遵循MyProtocol的类型创建Mock类时,需手动实现每个方法的调用计数、参数捕获等样板代码,如README.md中所示例:
class MyProtocolMock: MyProtocol {
var sayHelloWithNameCallsCount = 0
var sayHelloWithNameReceivedName: String?
func sayHelloWith(name: String) {
sayHelloWithNameCallsCount += 1
sayHelloWithNameReceivedName = name
}
}
这种方式不仅耗时,还会导致代码膨胀和维护困难。
Sourcery的自动化方案
Sourcery通过模板定义代码生成规则,仅需为协议添加特定注解(Annotation),即可自动生成完整实现。例如,为协议添加AutoMockable注解后:
extension MyProtocol: AutoMockable {}
Sourcery将根据预设模板自动生成上述数百行Mock代码,实现逻辑可参考Sourcery/Templates/目录下的模板文件。
Sourcery的实时代码生成演示,修改模板后即时更新生成结果
Sourcery核心概念与协议代码生成原理
模板类型与协议实现
Sourcery支持三种模板类型,均可用于协议代码生成:
- Stencil模板:基于Django风格的模板语言,适合大多数场景。例如Equality.stencil实现了
Equatable协议的自动生成:
{% for type in types.implementing.AutoEquatable %}
extension {{ type.name }}: Equatable {
static func == (lhs: {{ type.name }}, rhs: {{ type.name }}) -> Bool {
{% for variable in type.variables %}
lhs.{{ variable.name }} == rhs.{{ variable.name }} &&
{% endfor %}
true
}
}
{% endfor %}
-
Swift模板:使用类EJS语法,支持复杂逻辑。例如SourceryTests/Stub/SwiftTemplates/Equality.swifttemplate。
-
JavaScript模板:基于EJS引擎,适合熟悉JS的开发者,如JSExport.ejs。
类型系统与协议扫描
Sourcery通过扫描源码构建类型元数据,可在模板中通过以下变量访问协议相关信息:
types.implementing.AutoEquatable:获取所有遵循AutoEquatable协议的类型type.protocolRequirements:获取协议的所有方法和属性要求variable.annotations:访问代码中的自定义注解
详细类型定义可参考docs/Classes/Protocol.html文档。
实战:自动生成协议Mock类
步骤1:定义模板文件
创建AutoMockable.stencil模板,放置于Templates/目录:
{% for protocol in types|protocol %}
class {{ protocol.name }}Mock: {{ protocol.name }} {
{% for method in protocol.methods %}
// MARK: - {{ method.name }}
var {{ method.name }}CallsCount = 0
var {{ method.name }}ReceivedArguments: ({{ method.parameters|map:"typeName"|join:", " }})?
func {{ method.name }}({{ method.parameters|join:", " }}) {
{{ method.name }}CallsCount += 1
{{ method.name }}ReceivedArguments = ({{ method.parameters|map:"name"|join:", " }})
}
{% endfor %}
}
{% endfor %}
步骤2:配置Sourcery
创建.sourcery.yml配置文件,指定源码、模板和输出路径:
sources:
- ./Sources
templates:
- ./Templates/AutoMockable.stencil
output:
./Generated/Mocks
步骤3:添加协议注解
在源码中标记需要生成Mock的协议:
// sourcery: AutoMockable
protocol APIClient {
func fetchUser(id: Int) -> User
}
步骤4:运行Sourcery
执行命令生成代码:
sourcery --config .sourcery.yml
生成的Mock类将输出至./Generated/Mocks/AutoMockable.generated.swift,包含方法调用计数和参数捕获逻辑。完整流程可参考guides/Usage.md。
高级技巧:自定义协议注解与条件生成
注解驱动的代码生成
通过自定义注解可实现更精细的控制。例如,在模板中使用skipEquality注解排除特定属性:
{% for variable in type.variables|!annotated:"skipEquality" %}
if self.{{ variable.name }} != rhs.{{ variable.name }} { return false }
{% endfor %}
在源码中标记需要排除的属性:
// sourcery: skipEquality
var temporaryData: Data?
多协议组合生成
Sourcery支持同时处理多个协议,例如为遵循AutoEquatable和AutoHashable的类型生成代码:
{% for type in types.implementing.AutoEquatable & AutoHashable %}
extension {{ type.name }}: Equatable, Hashable {
// 实现代码
}
{% endfor %}
详细语法可参考guides/Writing templates.md中的"Annotation"章节。
项目集成与最佳实践
集成方式
Sourcery支持多种集成方式,满足不同项目需求:
- 命令行工具:手动执行或集成到CI流程,如README.md所述。
- Xcode插件:通过Plugins/SourceryCommandPlugin/在Xcode中直接运行。
- Build Phase:添加自定义Build Phase自动生成代码:
"$PODS_ROOT/Sourcery/bin/sourcery" --config .sourcery.yml
性能优化
对于大型项目,可通过以下方式提升Sourcery运行速度:
- 使用
--cacheBasePath指定缓存目录 - 通过
.sourcery.yml的exclude配置排除无关文件 - 启用
--watch模式实现增量生成
性能测试数据可参考SourceryTests/Sourcery+PerformanceSpec.swift。
总结与扩展应用
Sourcery通过元编程技术,将协议定向编程的潜力最大化,主要优势包括:
- 减少重复劳动:自动生成协议实现代码,平均减少30%的样板代码
- 提升代码一致性:确保协议实现遵循统一标准
- 支持复杂场景:通过模板逻辑处理条件生成、注解解析等高级需求
除协议实现外,Sourcery还可用于:
项目完整文档可参考docs/目录,更多模板示例见Templates/。立即通过以下命令开始使用Sourcery:
git clone https://gitcode.com/gh_mirrors/so/Sourcery
cd Sourcery
swift build -c release
通过Sourcery,让协议定向编程真正成为Swift开发的效率倍增器。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考




