ResultBuilder 学习笔记(一)

时间:2024-03-24 18:44:22

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()
}

通过前面的对比,我们已经了解,采用ResultBuilder技术的 concatBuilder 已经比concat()函数优越很多,但它仍然非常简单,我们的拼接需求会变化,我们需要更复杂的功能,比如,我们需要支持if-else语句,或者循环功能。如下所示:

var include = true 
let str = concatBuilder { 
  "春眠不觉晓"
   
   for i in 1...5
   {
     "处处闻啼鸟"
   }
   if include 
   { 
   "夜来风雨声" 
   }
   else
   {
     "花落知多少"
   }
}

这些都可以通过在ConcatBuilder结构体中实现更多的的buildXXX()方法来实现。在后续博文中,作者会通过更多示例,介绍如何用ResultBuilder实现上述功能。敬请关注。