ResultBuilder 学习笔记(一)
ResultBuilder 是Swift 语言的一个非常重要、强大特性,允许开发者以声明方式实现简洁、清晰、优雅的代码。在 Swift 5.4 之前,它被称为@functionBuilder
,之后被正式更名为@resultBuilder
。
使用ResultBuilder技术可以在Swift 中非常容易创建领域特定语言( DSL)。DSL 允许开发人员使用更自然且特定于领域的语法来执行特定任务,例如 SwiftUI 中的 UI 组件构建,从而使代码更易于编写、阅读和维护。
估计许多初学者也和作者开初一样,看了上面描述,对ResultBuilder是什么还是一头雾水。别急,下面我们通过一个简单示例来一一说明。 我们的例子很简单,字符串拼接,将个数不定的字符串拼接起来,形成一个新的字符串。我们比较一下,采用和不采用ResultBuilder这两种情形下有什么差别。
先不采用ResultBuilder。如下函数实现了上述拼接功能:
func concat(_ components: String...) ->String
{
return components.joined(separator: " ")
}
代码非常简单。如下是使用该函数:
let str = concat("春眠不觉晓","处处闻啼鸟", "夜来风雨声","花落知多少")
print(str)
上述代码其实没有什么问题。但是,如果采用下面的ResultBuilder方式是不是更好一些呢?
let str = concatBulder {
"春眠不觉晓"
"处处闻啼鸟"
"夜来风雨声"
"花落知多少"
}
print(str)
与前一种方式比较,后一种方式显然更简洁,自然,你也许会说,把第一种方式换成多行书写,不是一样吗?
let str = concat (
"春眠不觉晓",
"处处闻啼鸟",
"夜来风雨声",
"花落知多少"
)
print(str)
还真的不一样。首先逗号是多余的,不简洁。其次是小括号,将小括号的内容分解到多行不是很好的实践。
将大括号内容写在一行上也不是好的实践。
如果我们考虑给拼接功能增加一个分隔符,会是怎么样的呢?
第一种方式是这样的:
let str = concat (
"-",
"春眠不觉晓",
"处处闻啼鸟",
"夜来风雨声",
"花落知多少"
)
print(str)
显然,问题更严重了。分隔符和正常字符串容易混淆,给使用带来困扰。 加一个参数名separator
又如何呢,如下:
let str = concat (
separator:"-",
"春眠不觉晓",
"处处闻啼鸟",
"夜来风雨声",
"花落知多少"
)
print(str)
这下,分隔符似乎清楚了,但感觉还是怪怪的,总之还是别扭。
而采用ResultBuilder方式是这样的:
let str = concatBuilder(”-“) {
"春眠不觉晓"
"处处闻啼鸟"
"夜来风雨声"
"花落知多少"
}
print(str)
可以看到后者的代码仍然保持简洁、清晰,优雅。
通过上面比较可以看到,即便对这样一个简单的问题,如果稍稍扩展一下(需求),无论如何调整,用普通方式都容易产生不太好的代码。解决办法就是ResultBuilder,它的一个最大的优势就是有助于开发者写出简洁、清晰、优雅的代码。
如何实现
ResultBuilder 技术并不是一组协议,而是一组静态方法。 根据应用需求,我们必须实现其中的一个或者多个方法,而且,必须至少有一个buildBlock()
方法。
静态方法 | 简要说明 |
buildBlock(…) | 构建顺序语句块 |
buildOptional (…) | 构建没有 else 的 if 语句 |
buildEither(first: )和buildEither(second: ) | 构建if-else 语句 |
buildArray (…) | 构建循环语句 |
buildExpression (…) | 构建表达式语句 |
buildFinalResult (…) | 将临时内部类型转换为最终外部类型 |
通常,我们总是在枚举或者结构体中实现相应的方法。对我们的字符串拼接需求,目前我们只需要实现一个buildBlock()
方法即可。
如下是实现代码:
@resultBuilder
struct ConcatBuilder {
static func buildBlock(_ components: String...) -> String {
return components.joined(separator: "")
}
}
完毕,就这么简单,我们实现了字符串拼接功能的ResultBuilder版本,它和我们常见的ViewBuilder
完全类似,当然啰,功能比后者简单许多。 用上述 ConcatBuilder
改造 concat()
函数, 新函数命名为concatBuilder()
。如下:
func concatBuilder(@ConcatBuilder _ builder: () -> String) -> String {
return builder()
}
也可以将ConcatBuilder()
直接作用在变量上,如下:
@ConcatBuilder var str:String {
"春眠不觉晓"
"处处闻啼鸟"
"夜来风雨声"
"花落知多少"
}
通过前面的对比,我们已经了解,采用ResultBuilder技术的 concatBuilder
已经比concat()
函数优越很多,但它仍然非常简单,我们的拼接需求会变化,我们需要更复杂的功能,比如,我们需要支持if-else
语句,或者循环功能。如下所示:
var include = true
let str = concatBuilder {
"春眠不觉晓"
for i in 1...5
{
"处处闻啼鸟"
}
if include
{
"夜来风雨声"
}
else
{
"花落知多少"
}
}
这些都可以通过在ConcatBuilder
结构体中实现更多的的buildXXX()
方法来实现。在后续博文中,作者会通过更多示例,介绍如何用ResultBuilder实现上述功能。敬请关注。