ResultBuilder 学习笔记(一)

ResultBuilder 学习笔记(一)

ResultBuilder 是Swift 语言的一个非常重要、强大特性,允许开发者以声明方式实现简洁、清晰、优雅的代码。在 Swift 5.4 之前,它被称为@functionBuilder,之后被正式更名为@resultBuilder

使用ResultBuilder技术可以在Swift 中非常容易创建领域特定语言( DSL)。DSL 允许开发人员使用更自然且特定于领域的语法来执行特定任务,例如 SwiftUI 中的 UI 组件构建,从而使代码更易于编写、阅读和维护。

估计许多初学者也和作者开初一样,看了上面描述,对ResultBuilder是什么还是一头雾水。别急,下面我们通过一个简单示例来一一说明。 我们的例子很简单,字符串拼接,将个数不定的字符串拼接起来,形成一个新的字符串。我们比较一下,采用和不采用ResultBuilder这两种情形下有什么差别。

先不采用ResultBuilder。如下函数实现了上述拼接功能:

swift 复制代码
func concat(_ components: String...) ->String
{
    return components.joined(separator: " ")
}

代码非常简单。如下是使用该函数:

swift 复制代码
let str = concat("春眠不觉晓","处处闻啼鸟", "夜来风雨声","花落知多少") 
print(str)

上述代码其实没有什么问题。但是,如果采用下面的ResultBuilder方式是不是更好一些呢?

swift 复制代码
let str = concatBulder {
"春眠不觉晓"
"处处闻啼鸟"
"夜来风雨声"
"花落知多少" 
}
print(str)

与前一种方式比较,后一种方式显然更简洁,自然,你也许会说,把第一种方式换成多行书写,不是一样吗?

swift 复制代码
let str = concat (
  "春眠不觉晓",
  "处处闻啼鸟",
  "夜来风雨声",
  "花落知多少"
) 
print(str)

还真的不一样。首先逗号是多余的,不简洁。其次是小括号,将小括号的内容分解到多行不是很好的实践。

将大括号内容写在一行上也不是好的实践。

如果我们考虑给拼接功能增加一个分隔符,会是怎么样的呢?

第一种方式是这样的:

swift 复制代码
let str = concat (
  "-",
  "春眠不觉晓",
  "处处闻啼鸟",
  "夜来风雨声",
  "花落知多少"
) 
print(str)

显然,问题更严重了。分隔符和正常字符串容易混淆,给使用带来困扰。 加一个参数名separator又如何呢,如下:

swift 复制代码
let str = concat (
  separator:"-",
  "春眠不觉晓",
  "处处闻啼鸟",
  "夜来风雨声",
  "花落知多少"
) 
print(str)

这下,分隔符似乎清楚了,但感觉还是怪怪的,总之还是别扭。

而采用ResultBuilder方式是这样的:

swift 复制代码
let str = concatBuilder("-") { 
  "春眠不觉晓"
  "处处闻啼鸟"
  "夜来风雨声"
  "花落知多少"
}
print(str)

可以看到后者的代码仍然保持简洁、清晰,优雅。

通过上面比较可以看到,即便对这样一个简单的问题,如果稍稍扩展一下(需求),无论如何调整,用普通方式都容易产生不太好的代码。解决办法就是ResultBuilder,它的一个最大的优势就是有助于开发者写出简洁、清晰、优雅的代码。

如何实现

ResultBuilder 技术并不是一组协议,而是一组静态方法。 根据应用需求,我们必须实现其中的一个或者多个方法,而且,必须至少有一个buildBlock()方法。

静态方法 简要说明
buildBlock(...) 构建顺序语句块
buildOptional (...) 构建没有 else 的 if 语句
buildEither(first: )和buildEither(second: ) 构建if-else 语句
buildArray (...) 构建循环语句
buildExpression (...) 构建表达式语句
buildFinalResult (...) 将临时内部类型转换为最终外部类型

通常,我们总是在枚举或者结构体中实现相应的方法。对我们的字符串拼接需求,目前我们只需要实现一个buildBlock()方法即可。

如下是实现代码:

swift 复制代码
@resultBuilder
struct ConcatBuilder {
    static func buildBlock(_ components: String...) -> String {
        return components.joined(separator: "")
    }
}

完毕,就这么简单,我们实现了字符串拼接功能的ResultBuilder版本,它和我们常见的ViewBuilder完全类似,当然啰,功能比后者简单许多。 用上述 ConcatBuilder 改造 concat() 函数, 新函数命名为concatBuilder()。如下:

swift 复制代码
func concatBuilder(@ConcatBuilder _ builder: () -> String) -> String {
    return builder()
}

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

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

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

相关推荐
大熊猫侯佩11 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(五)
swiftui·swift·apple watch
大熊猫侯佩1 天前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(三)
数据库·swiftui·swift
大熊猫侯佩1 天前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(二)
数据库·swiftui·swift
大熊猫侯佩2 天前
用异步序列优雅的监听 SwiftData 2.0 中历史追踪记录(History Trace)的变化
数据库·swiftui·swift
大熊猫侯佩2 天前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(一)
数据库·swiftui·swift
season_zhu2 天前
iOS开发:关于日志框架
ios·架构·swift
大熊猫侯佩2 天前
SwiftUI 中如何花样玩转 SF Symbols 符号动画和过渡特效
swiftui·swift·apple
大熊猫侯佩2 天前
SwiftData 共享数据库在 App 中的改变无法被 Widgets 感知的原因和解决
swiftui·swift·apple
大熊猫侯佩2 天前
使用令牌(Token)进一步优化 SwiftData 2.0 中历史记录追踪(History Trace)的使用
数据库·swift·apple
大熊猫侯佩2 天前
SwiftUI 在 iOS 18 中的 ForEach 点击手势逻辑发生改变的解决
swiftui·swift·apple