一个为 Swift 和 Foundation 值类型提供实用 Result Builders 的集合。
在 Swift 中,数组、字典和其他基于集合的类型相对容易构建和修改。
然而,当包含的元素依赖于某些条件或复杂的逻辑时,事情就会变得棘手。一个典型的例子是构建要发送到分析服务的有效负载。结果代码可能如下所示:
func checkoutAnalyticsEvent(didSucceed: Bool, purchaseAmount: Decimal, userId: String?) -> [String: String] {
var event: [String: String] = [:]
event["success"] = didSucceed ? "true" : "false"
if purchaseAmount > 0 {
event["amount"] = purchaseAmount.formatted(.number.precision(.fractionLength(2)))
} else {
event["isFree"] = "true"
}
if let userId = userId {
event["userId"] = userId
} else {
event["isGuest"] = "true"
}
return event
}
这还不错,但绝对没有人们期望的那么 Swifty。
我们正在将命令式代码洒在我们应该只是有效负载描述的地方。这不仅使一眼就能推断代码变得更加困难,而且还为意外的修改留下了太多的余地。
幸运的是,有一种更好的方法...
Swift Builders 允许为 Swift 和 Foundation 中的大多数 Collection
类型使用 result builder 语法。
例如,通过利用 Dictionary.build
,我们上面的用例变为:
import Builders
func checkoutAnalyticsEvent(didSucceed: Bool, purchaseAmount: Decimal, userId: String?) -> [String: String] {
return [String: String].build {
["success": didSucceed ? "true" : "false"]
if purchaseAmount > 0 {
["amount": purchaseAmount.formatted(.number.precision(.fractionLength(2)))]
} else {
["isFree": "true"]
}
if let userId = userId {
["userId": userId]
} else {
["isGuest": "true"]
}
}
}
我们甚至可以使用 @DictionaryBuilder
属性注释我们的函数,使函数体像 builder 本身一样工作(想想 @ViewBuilder
)
import Builders
@DictionaryBuilder<String, String>
func checkoutAnalyticsEvent(didSucceed: Bool, purchaseAmount: Decimal, userId: String?) -> [String: String] {
["success": didSucceed ? "true" : "false"]
if purchaseAmount > 0 {
["amount": purchaseAmount.formatted(.number.precision(.fractionLength(2)))]
} else {
["isFree": "true"]
}
if let userId = userId {
["userId": userId]
} else {
["isGuest": "true"]
}
}
这只是将 result builders 应用于 Swift 本地类型的强大功能的一个小演示。
该库开箱即用地提供了各种 builders:
ArrayBuilder
ArraySliceBuilder
ContiguousArrayBuilder
DataBuilder
DictionaryBuilder
SetBuilder
SliceBuilder
StringBuilder
StringUTF8ViewBuilder
StringUnicodeScalarViewBuilder
SubstringBuilder
SubstringUTF8ViewBuilder
SubstringUnicodeScalarViewBuilder
MacBook Pro (14-inch, 2021)
Apple M1 Pro (10 cores, 8 performance and 2 efficiency)
32 GB Memory
$ swift run -c release Benchmarks
name time std iterations
-------------------------------------------------------------------
Array<Any>.build 1833.000 ns ± 7.60 % 757726
Array<Int>.build 542.000 ns ± 15.49 % 1000000
Array<Int?>.build 709.000 ns ± 9.51 % 1000000
ArraySlice<Any>.build 2750.000 ns ± 5.28 % 511759
ArraySlice<Int>.build 875.000 ns ± 8.40 % 1000000
ArraySlice<Int?>.build 1167.000 ns ± 13.55 % 1000000
ContiguousArray<Any>.build 1917.000 ns ± 12.37 % 729365
ContiguousArray<Int>.build 542.000 ns ± 23.24 % 1000000
ContiguousArray<Int?>.build 750.000 ns ± 13.97 % 1000000
Data.build 875.000 ns ± 13.55 % 1000000
Dictionary<String, Any>.build 4209.000 ns ± 6.26 % 328025
Dictionary<String, Double>.build 2459.000 ns ± 11.92 % 562007
Dictionary<String, Double?>.build 2583.000 ns ± 5.51 % 526636
Set<Any>.build 6333.000 ns ± 10.30 % 228224
Set<Int>.build 750.000 ns ± 11.22 % 1000000
Set<Int?>.build 1292.000 ns ± 11.42 % 1000000
Slice<Array<Any>>.build 2209.000 ns ± 4.95 % 629537
Slice<Array<Int>>.build 584.000 ns ± 17.10 % 1000000
Slice<Array<Int?>>.build 917.000 ns ± 8.49 % 1000000
String.build 500.000 ns ± 8.91 % 1000000
String.UnicodeScalarView.build 3958.000 ns ± 3.04 % 351918
String.UTF8View.build 542.000 ns ± 10.33 % 1000000
Substring.build 1709.000 ns ± 4.41 % 810685
Substring.UnicodeScalarView.build 5084.000 ns ± 3.19 % 274560
Substring.UTF8View.build 1333.000 ns ± 5.89 % 1000000