3

iOS 15 中的新功能,我们可以像这样形成一个 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 的行为方式相同?

4

1 回答 1

2

我过去制作的 DSL 与此类似。

我无法验证这正是他们正在做的事情,但我可以描述我实现类似 DSL 语法的方式。

我的构建器对象将具有类似的方法.font.color返回一个临时的@dynamicCallable struct. 这些结构将存储它们的父构建(以此类推, the 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,您可以使用一些通用的魔法和键路径(而不是字符串)来为每个键路径返回不同的类型,这callAsFunciton可能需要不同类型的参数,这可以在编译时强制执行。

您可以使用@dynamicCallable而不是@dynamicMemberLookup+ callAsFunction,但我不认为我刚才提到的技巧有效。

于 2021-06-27T23:53:42.033 回答