• 你们有考虑过在Swift中使用宏吗?
  • 发布于 4天前
  • 121 热度
    0 评论
  • 卧龙生
  • 1 粉丝 56 篇博客
  •   
前言
昨天刷到多邻国官方博客的一篇技术文章,他们居然用 Swift 宏把 MVVM 架构的样板代码问题给完美解决了,而且还自动生成了 4300 多行代码!今天就来聊聊多邻国是怎么用 Swift 宏这个"黑科技"来拯救 MVVM 架构的,顺便看看咱们能不能偷师一两招。

问题的起因:MVVM 好是好,就是太费手
多邻国的 iOS 团队这几年疯狂扩张,代码量也是蹭蹭往上涨。为了应对这种规模,他们的客户端架构团队开始推广标准化的 MVVM 架构。结果推广过程中,开发者们开始抱怨了:"你们这 MVVM 确实挺好的,但是样板代码也太多了吧"!这时候他们想起了 Swift 的一个"杀手锏"功能:宏(Macros)。

Swift 宏到底是什么鬼?
简单说,宏就是在编译时自动生成代码的标签。
它的工作原理是这样的:
.宏会分析你写的代码的抽象语法树(AST)
.根据这些信息生成新的代码
.把生成的代码插入到你的类、结构体、方法里

.整个过程在编译时完成,生成的代码和手写代码没任何区别


听起来很高深?其实就是让编译器帮你写重复代码的工具。

实战案例:DataSource 层的救星
在多邻国的 MVVM 架构里,DataSource 层是个很重要的概念。它负责抽象各种数据源——本地文件、数据库、键值存储、远程 API、内存存储等等。这样设计的好处是:上层的 Repository 不用关心底层数据到底存在哪,测试也更简单。但问题是,这些 DataSource 的实现模式都差不多,导致大量重复代码。
比如这两个方法:
public func updateValueA(valueA: Int, userID: DUOUserID)throws {
let store = store(for: userID)
try store.set(valueA, forKey: .valueAKey)
}

publicfunc updateValueB(valueB: String, userID: DUOUserID)throws {
let store = store()
try store.set(valueB, forKey: .valueBKey)
}
看出来了吗?除了参数名和 key 不一样,其他都一模一样!你可能会想:"为啥不写个泛型方法 updateValue<T>(...)?"
问题是这样做会把实现细节(key 的选择)暴露给调用方,违背了抽象的初衷。所以,这种情况就是宏的完美使用场景。

宏是怎么实现的?
多邻国的做法很聪明,他们先定义了一个 key 的扩展:
private extension KeyValueStoreItem.Key {
    static let avatar = Key<Avatar?>("avatar")
}
然后把 GenerateKeyValueDataSource 宏附加到这个扩展上,就能自动生成完整的 KeyValueDataSource 实现!

图片中红框中为自动生成的代码。宏会分析这些 key 定义,然后自动生成对应的 get、set、update 等方法。一个扩展搞定,几十个方法自动生成。

宏的好处:不只是偷懒这么简单
用宏的好处可不只是少写代码:
节省时间:4300 行代码不用手写,这得省多少工时?
减少错误:机器生成的代码比人写的更不容易出 bug
统一风格:所有 DataSource 都是同一套模板生成,代码风格完全一致
容易维护:要改逻辑?改宏就行了,所有地方自动更新

不用测试:宏本身测试过了,生成的代码不用再写单测


但宏也不是万能的
Swift 宏虽然强大,但也有一些缺点:
生成的代码在 Xcode 里搜不到:想找某个方法?不好意思,搜不到
增加复杂度:新手看到宏可能一脸懵逼

调试困难:出了问题不太好定位是宏的问题还是你的问题


所以多邻国的建议是:能用辅助函数解决的,优先用辅助函数。实在没办法了,再考虑宏。

构建时间:痛并快乐着
刚开始用宏的时候,他们发现清理构建时间增加了 10-20 秒。
后来他们想了个办法:
.不用 Swift Package Manager 导入宏包
.手动链接二进制文件,用 load-plugin-executable 标志
.通过 Makefile 显式构建,只在本地改动时才重新构建
这样基本保证了除非你直接改宏包,否则永远不用重新构建。

成果:4300 行代码的胜利
据他们介绍,现在多邻国的 iOS 代码库里有超过 4300 行宏生成的代码。而且他们还在继续扩大宏的使用范围,现在已经开始用宏来自动生成协议、指定初始化器等其他样板代码了。

对我们的启发
看完多邻国的实践,我觉得有几个值得借鉴的地方:
先标准化,再自动化:他们先统一了 MVVM 架构,再用宏解决重复代码问题
从最痛点开始:DataSource 层重复代码最多,所以先从这里下手

构建优化很重要:要考虑宏对构建时间的影响,提前优化


Swift 宏值得学吗?
我的观点是:绝对值得!虽然 Swift 宏已经推出很长一段时间了,但据我所知使用的人并不多,我觉得并不是这项技术不够好,而是太难了,会的人不多。但这正是机会。掌握了这个技能,你就能比别人更厉害。特别是现在大家都在讨论 AI 写代码,其实宏就是一种更可控、更可靠的"AI 写代码"方式。

你的项目里有没有考虑过用 Swift 宏?评论区聊聊!
用户评论