iOS 15 AttributeContainer 函数链是如何工作的?
How does iOS 15 AttributeContainer function chaining work?
iOS15 中的新功能,我们可以像这样形成一个 Swift AttributedString:
var att = AttributedString("Howdy")
att.font = UIFont(name:"Arial-BoldMT", size:15)
att.foregroundColor = UIColor(red:0.251, green:0.000, blue:0.502, alpha:1)
print(att)
很酷,但还有另一种方法。我们可以通过 AttributeContainer 的方式创建属性字典,而不是连续的命令式 属性 设置,将修饰函数链接到 AttributeContainer 以形成字典:
let att2 = AttributedString("Howdy",
attributes: AttributeContainer()
.font(UIFont(name:"Arial-BoldMT", size:15)!)
.foregroundColor(UIColor(red:0.251, green:0.000, blue:0.502, alpha:1))
)
print(att2)
(在现实生活中我会说 .init()
而不是 AttributeContainer()
。)
所以我的问题是,这在语法上是如何工作的?我们这里似乎有一个 DSL,我们可以在其中根据属性键的名称链接看起来像函数调用的内容。在幕后,似乎有一些动态成员查找的组合,callAsFunction
,也许还有某种中间构建器对象。我可以看到每个 callAsFunction
调用都返回 AttributeContainer,这显然是链接的工作方式。但是,我们将如何编写自己的对象,使其在句法上的行为与 AttributeContainer 的行为方式相同?
我以前做过类似的DSL。
我无法验证这正是他们在做的事情,但我可以描述我实现类似 DSL 语法的方式。
我的构建器对象会有 .font
和 .color
return 之类的临时方法 @dynamicCallable struct
。这些结构将存储它们的父构建(通过类比,AttributeContainer
),以及它们被调用的键路径源自(\.font
、\.color
等)。 (我不记得我是否使用了正确的键路径或字符串。我可以稍后检查并回复你。)
callAsFunction
的实现类似于:
func callAsFunction(_ someParam: SomeType) -> AttributeContainer {
parent[keyPath: keyPath] = someParam
return parent // for further chaining in the fluent interface.
}
随后的调用,例如 .foregroundColor
将重复相同的过程。
这是一个简单的示例:
@dynamicMemberLookup struct DictBuilder<Value> {
struct Helper<Value> {
let key: String
var parent: DictBuilder<Value>
func callAsFunction(_ value: Value) -> DictBuilder<Value> {
var copy = parent
copy.dict[key] = value
return copy
}
}
var dict = [String: Value]()
subscript(dynamicMember key: String) -> Helper<Value> {
return DictBuilder.Helper(key: key, parent: self)
}
}
let dict = DictBuilder<Int>()
.a(1)
.b(2)
.c(3)
.dict
print(dict)
IIRC,你可以将一些通用的魔法和键路径(而不是字符串)转换成每个键路径 return 不同的类型,其 callAsFunciton
可能需要不同类型的参数,这可以在编译时强制执行.
您可以使用 @dynamicCallable
而不是 @dynamicMemberLookup
+callAsFunction
,但我认为我刚才提到的技巧不起作用。
iOS15 中的新功能,我们可以像这样形成一个 Swift AttributedString:
var att = AttributedString("Howdy")
att.font = UIFont(name:"Arial-BoldMT", size:15)
att.foregroundColor = UIColor(red:0.251, green:0.000, blue:0.502, alpha:1)
print(att)
很酷,但还有另一种方法。我们可以通过 AttributeContainer 的方式创建属性字典,而不是连续的命令式 属性 设置,将修饰函数链接到 AttributeContainer 以形成字典:
let att2 = AttributedString("Howdy",
attributes: AttributeContainer()
.font(UIFont(name:"Arial-BoldMT", size:15)!)
.foregroundColor(UIColor(red:0.251, green:0.000, blue:0.502, alpha:1))
)
print(att2)
(在现实生活中我会说 .init()
而不是 AttributeContainer()
。)
所以我的问题是,这在语法上是如何工作的?我们这里似乎有一个 DSL,我们可以在其中根据属性键的名称链接看起来像函数调用的内容。在幕后,似乎有一些动态成员查找的组合,callAsFunction
,也许还有某种中间构建器对象。我可以看到每个 callAsFunction
调用都返回 AttributeContainer,这显然是链接的工作方式。但是,我们将如何编写自己的对象,使其在句法上的行为与 AttributeContainer 的行为方式相同?
我以前做过类似的DSL。
我无法验证这正是他们在做的事情,但我可以描述我实现类似 DSL 语法的方式。
我的构建器对象会有 .font
和 .color
return 之类的临时方法 @dynamicCallable struct
。这些结构将存储它们的父构建(通过类比,AttributeContainer
),以及它们被调用的键路径源自(\.font
、\.color
等)。 (我不记得我是否使用了正确的键路径或字符串。我可以稍后检查并回复你。)
callAsFunction
的实现类似于:
func callAsFunction(_ someParam: SomeType) -> AttributeContainer {
parent[keyPath: keyPath] = someParam
return parent // for further chaining in the fluent interface.
}
随后的调用,例如 .foregroundColor
将重复相同的过程。
这是一个简单的示例:
@dynamicMemberLookup struct DictBuilder<Value> {
struct Helper<Value> {
let key: String
var parent: DictBuilder<Value>
func callAsFunction(_ value: Value) -> DictBuilder<Value> {
var copy = parent
copy.dict[key] = value
return copy
}
}
var dict = [String: Value]()
subscript(dynamicMember key: String) -> Helper<Value> {
return DictBuilder.Helper(key: key, parent: self)
}
}
let dict = DictBuilder<Int>()
.a(1)
.b(2)
.c(3)
.dict
print(dict)
IIRC,你可以将一些通用的魔法和键路径(而不是字符串)转换成每个键路径 return 不同的类型,其 callAsFunciton
可能需要不同类型的参数,这可以在编译时强制执行.
您可以使用 @dynamicCallable
而不是 @dynamicMemberLookup
+callAsFunction
,但我认为我刚才提到的技巧不起作用。