定义
结果构造器: 以声明式的语法构建数组等嵌套式的数据。
优点
- 使代码的结构清晰,提高可读性。
- 减少了临时变量的使用
举例
实现设置菜单列表:
import Foundation
struct Setting {
enum Value {
case int(Int)
case bool(Bool)
case group([Setting])
}
var name: String
var value: Value
}
@resultBuilder
struct SettingsBuilder {
static func buildBlock(_ settings: Setting...) -> [Setting] { settings }
}
@SettingsBuilder func getSettings() -> [Setting] {
Setting(name: "option 1", value: .int(1))
Setting(name: "option 2", value: .bool(true))
Setting(name: "option 3", value: .group([
Setting(name: "option 3.1", value: .bool(true)),
Setting(name: "option 3.2", value: .bool(true))
]))
}
let settings = getSettings()
print(settings)
Result Builder的使用有两个基本原则:
- 必须使用@resultBuilder修饰符修饰
- 必须至少实现一个buildBlock静态方法
进阶
支持if语句
import Foundation
protocol SettingsConvertible {
func asSettings() -> [Setting]
}
struct Setting: SettingsConvertible {
enum Value {
case int(Int)
case bool(Bool)
case group([Setting])
}
var name: String
var value: Value
func asSettings() -> [Setting] { [self] }
}
extension Array: SettingsConvertible where Element == Setting {
func asSettings() -> [Setting] { self }
}
@resultBuilder
struct SettingsBuilder {
static func buildBlock(_ component: SettingsConvertible...) -> [Setting] {
return component.flatMap{ $0.asSettings() }
}
static func buildOptional(_ component: SettingsConvertible?) -> SettingsConvertible {
component ?? []
}
}
let flag1 = false
@SettingsBuilder func getSettings() -> [Setting] {
Setting(name: "option 1", value: .int(1))
if flag1 {
Setting(name: "option 2", value: .bool(true))
}
Setting(name: "option 3", value: .bool(true))
}
let settings = getSettings()
print(settings)
原理伪代码:
let s1 = Setting(name: "option 1", value: .int(1))
let if_1: Setting? = nil
if flag {
let s2 = Setting(name: "option 2", value: .bool(true))
if_1 = SettingsBuilder.buildBlock(s2)
}
let r_1 = SettingsBuilder.buildOptional(if_1)
let s3 = Setting(name: "option 3", value: .bool(true))
let result = SettingsBuilder.buildBlock(s1, r_1, s3)
从上面可以看出:
- 构造总是从上往下执行的
- if分支内部会单独执行一次buildBlock,并将buildBlock的结果作为入参传给buildOptional
- 最后会将普通构造结果和buildOptional的输出一起作为入参调用buildBlock
- buildBlock的出参会作为buildOptional的入参,buildOptional的出参又会作为buildBlock的入参,因此引入了协议SettingsConvertible
支持if/else语句或switch/case语句
需要实现buildEither(first:)和buildEither(second:)方法
import Foundation
protocol SettingsConvertible {
func asSettings() -> [Setting]
}
struct Setting: SettingsConvertible {
enum Value {
case int(Int)
case bool(Bool)
case group([Setting])
}
var name: String
var value: Value
func asSettings() -> [Setting] { [self] }
}
extension Array: SettingsConvertible where Element == Setting {
func asSettings() -> [Setting] { self }
}
@resultBuilder
struct SettingsBuilder {
static func buildBlock(_ component: SettingsConvertible...) -> [Setting] {
return component.flatMap{ $0.asSettings() }
}
static func buildEither(first component: SettingsConvertible) -> SettingsConvertible {
return component
}
static func buildEither(second component: SettingsConvertible) -> SettingsConvertible {
return component
}
}
let flag1 = false
@SettingsBuilder func getSettings() -> [Setting] {
Setting(name: "option 1", value: .int(1))
if flag1 {
Setting(name: "option 2", value: .bool(true))
} else {
Setting(name: "option 3", value: .bool(true))
}
Setting(name: "option 4", value: .bool(true))
}
let settings = getSettings()
print(settings)
在闭包中使用
import Foundation
protocol SettingsConvertible {
func asSettings() -> [Setting]
}
struct Setting: SettingsConvertible {
enum Value {
case int(Int)
case bool(Bool)
case group([Setting])
}
var name: String
var value: Value
func asSettings() -> [Setting] { [self] }
}
struct SettingGroup: SettingsConvertible {
var name: String
var settings: [Setting]
init(name: String, @SettingsBuilder builder: () -> [Setting]) {
self.name = name
self.settings = builder()
}
func asSettings() -> [Setting] {
[Setting(name: name, value: .group(settings))]
}
}
@resultBuilder
struct SettingsBuilder {
static func buildBlock(_ component: SettingsConvertible...) -> [Setting] {
return component.flatMap{ $0.asSettings() }
}
}
@SettingsBuilder func getSettings() -> [Setting] {
Setting(name: "option 1", value: .int(1))
Setting(name: "option 2", value: .bool(true))
SettingGroup(name: "option 3") {
Setting(name: "option 3.1", value: .bool(true))
Setting(name: "option 3.2", value: .bool(true))
}
}
let settings = getSettings()
print(settings)
在for…in语句中使用
import Foundation
protocol SettingsConvertible {
func asSettings() -> [Setting]
}
struct Setting: SettingsConvertible {
enum Value {
case int(Int)
case bool(Bool)
case group([Setting])
}
var name: String
var value: Value
func asSettings() -> [Setting] { [self] }
}
extension Array: SettingsConvertible where Element == Setting {
func asSettings() -> [Setting] { self }
}
@resultBuilder
struct SettingsBuilder {
static func buildBlock(_ components: SettingsConvertible...) -> [Setting] {
components.flatMap{ $0.asSettings() }
}
static func buildArray(_ components: [[Setting]]) -> [Setting] {
components.flatMap { $0 }
}
}
@SettingsBuilder func getSettings() -> [Setting] {
for index in 1...3 {
Setting(name: "option \(index)", value: .bool(true))
}
}
let settings = getSettings()
print(settings)
原理伪代码:
var array = [[Setting]]()
for index in 1...3 {
let s = Setting(name: "option \(index)", value: .bool(true))
let s_1 = SettingsBuilder.buildBlock(s)
array.append(s_1)
}
let r1 = SettingsBuilder.buildArray(array)
let result = SettingsBuilder.buildBlock(r1)
从上面可以看出:
- 构造总是从上往下执行的
- for内部会单独执行一次buildBlock,并将buildBlock的多次执行的结果合成一个数组
- 最后会将上述数组作为入参调用buildArray
- 最后会将普通构造结果和buildArray的输出一起作为入参调用buildBlock
- buildBlock的出参合成一个数组作为buildArray的入参,buildArray的出参又会作为buildBlock的入参
应用场景
- @ViewBuilder
- HTML-DSL
- Request-DSL
- awesome-result-builders