ExpressibleBy
表示Swift标准库中的一系列协议,它允许您直接从令牌文字实例化对象,如字符串,数字,浮点等,如果对象可以像这样“表达”。例如,以下是在Swift中创建URL的常规方法:
点击此处进交流群 有技术的来闲聊 没技术的来学习
func getURL() -> URL
复制代码
但是,为了防止每次都必须使用此初始化程序,您可以说可以使用ExpressibleByStringLiteral
以下命令直接从其URL字符串表示URL :
extension URL: ExpressibleByStringLiteral {
复制代码
这允许我们重构以使用除字符串标记之外的任何其他内容来创建URL:getURL()
func getURL() -> URL
复制代码
标准库包含以下ExpressibleBy
协议:
* ExpressibleByNilLiteral
:表达方式nil
。 * ExpressibleByIntegerLiteral
:由数字标记表示10
。 * ExpressibleByFloatLiteral
:由浮点标记表示2.5
。 * ExpressibleByBooleanLiteral
:表达方式。 * :从单个unicode标量表示。用法示例是和。 * :与UnicodeScalar类似,但由一系列标量(字形集群)而不是单个标量组成。 * :由字符串标记表示。 * :由数组标记表示。 * :由字典标记表示。true/false
ExpressibleByUnicodeScalarLiteral
Character
String
ExpressibleByExtendedGraphemeClusterLiteral
ExpressibleByStringLiteral
"SwiftRocks"
ExpressibleByArrayLiteral
[1,2,3]
ExpressibleByDictionaryLiteral
["name": "SwiftRocks"]
简而言之,您可以使用这些协议隐藏不必要的实现细节,并可能隐藏更复杂类型的丑化初始化程序。一个示例用例是Apple的SourceKit-LSP如何使用它们来表示任意参数 - 因为Any
类型不符合Codable
,CommandArgumentType
枚举用于表示未知参数:
public enum CommandArgumentType: Hashable, ResponseType {
复制代码
但是,因为我们正在处理枚举,所以表示一个参数将导致不那么漂亮的代码行:
func getCommandArguments() -> CommandArgumentType {
复制代码
幸运的是,我们可以ExpressibleBy
用来为枚举提供更好看的替代方案:
extension CommandArgumentType: ExpressibleByNilLiteral {
复制代码
这使我们可以用更容易阅读的令牌重写。getCommandArguments()
func getCommandArguments() -> CommandArgumentType {
复制代码
内部如何工作
但令牌如何成为完整类型?与所有编译器魔法一样,我们可以通过拦截Swift的编译步骤来揭示正在发生的事情。
以第一种方法为例,让我们首先看看Swift如何处理ExpressibleBy对象。如果我们使用参数手动编译代码,我们可以提取代码的Swift中间语言(SIL)版本 - 在LLVM开始之前,Swift中的最终编译步骤。getURL()
-emit-sil
swiftc -emit-sil geturl.swift
复制代码
我编辑的输出使其更易于阅读,如下所示:
sil hidden @$s3bla6getURL10Foundation0C0VyF : $@convention(thin) () -> @out URL {
bb0(%0 : $*URL):
复制代码
这是方法正在做的事情:
1:创建string_literal
令牌 2:String
从文字 3 创建类型:使用 4 调用:返回URLURL.init(stringLiteral:)
String
正如人们所料,编译器通过用String
相关的ExpressibleBy
初始化程序替换代码行来实现这种魔力。万岁为编译魔术!
现在,为了找到编译器中发生这种情况的位置,我们可以grep
在Swift源中提到“ExpressibleBy”,它将指向CSApply.cpp中的几个位置。简而言之,文字的所有用法都被转换为它们的ExpressibleBy等价物,包括“本身就是文字的表达”(例如,a Int
本身就是一个ExpressibleByIntegerLiteral
)。当Swift的类型检查器到达文字时,它会获得相关协议类型的实例和初始化程序的名称,这可以从我们正在查看的文字中确定:
Expr *visitNilLiteralExpr(NilLiteralExpr *expr) {
复制代码
根据手中的信息,类型检查程序调用convertLiteralInPlace
来代替充分表达具有同等ExpressibleBy初始化。该方法本身做了很多事情,但这里有一些值得注意的事情:如果我们看看KnownProtocols.def,我们可以看到所有文字都有默认类型:
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByArrayLiteral, "Array", false)
复制代码
这意味着如果表达式没有类型或者类型不符合协议,则文本的真实类型将被分配给默认的类型一致性。例如,如果我删除了一致性,则SIL代码将显示使用内部初始化程序:getURL()``String
func getURL() -> URL {
复制代码
这不仅允许你编写类似的非类型化表达式,而且还可以用于UI原因 - 由于这一点,在后面的过程中,前面的例子将导致我们用户友好的编译错误。let foo = "bar"
getURL()
Cannot convert value of type'String' to specified type 'URL'