Swift Tips 006 - Using a name already taken by the standard library

每天了解一点不一样的 Swift 小知识

代码截图

小笔记

这段代码想传达什么意思

试想一下,假设你定义了一个 Command 类型,针对命令执行错误的状况,我们八成会用一个继承自 Error 类型的子类来描述错误类型,那么问题来了,这个类型的名字叫什么?

看起来 Error 是最简单优雅的,所以你的代码可能会写成如下的样式

extension Command {
enum Error: Error {
case missing
case invalid(String)
}
}

只是很可惜,这么写代码会报错,一种解决方案就是我们把代码改成

extension Command {
enum CommandError: Error {
case missing
case invalid(String)
}
}

这种方案虽然解决了问题,但不够优雅,主要是在类型的命名上,当我们要引用 Command 里面的这个内嵌类型时,我们会怎么写代码呢,必定是 Command.CommandError.missing ,看到这似乎嗅到了一点点 Objective-C 那又臭又长的命名味道,那么有更好的解决方案么?

有,也就是我们说的第二种方案,用 Swift. 来解决与命名冲突的问题,此时代码就需要这么写了

extension Command {
enum Error: Swift.Error {
case missing
case invalid(String)
}
}

第二种方案是怎么解决问题的

在正式解释前,我们需要知道今天的话题本质是命名冲突引起的,当然,这里还需要明确一点的就是,命名冲突有 2 种情况,一种是类型之间的冲突,一种是 module 级别的冲突,

对于类型之间的冲突,是说同一个 Framework 下,如何区分同名类型,这个使用 Swift 里面的嵌套类型就可以解决,下面是一个示例

// A.framework
struct Command {
struct Error: Swift.Error {}
}
struct Result {
struct Error: Swift.Error {}
}
let commandError = Command.Error()
let resultError = Result.Error()

而对于 Module 级别的冲突,是说如何区分 A Framework 和 B Framework 里面的同名类型,这个问题在 Swift 里也有解决的办法,通过指明类型所在的 Framework 即可。

// A.framework
enum Error {
}
// B.framework
enum Error {
}
let aError = A.Error()
let bError = B.Error()

上面的内容或多或少都与 Swift 命名空间的知识相关联,而关于 Swift 命名空间的官方文档,我并没有找到太多,只找到 2 份比较有分量的材料,感兴趣的同学可以看看:

Swift 之父 Chris 在 Twitter 上的一些说明:传送门

Matt 大神在 SO 上的回答:传送门

结合今天的内容来总结一下上面 2 位大牛表达的观点就是说:

针对 module 级别的命名冲突,Swift 会优先考虑你代码中的命名,如果你想要使用其他库的命名,需要显示的指明,例如用 Swift.Error 或者 Foundation.NSArray 来指明这个类型不是当前代码中的类型。

好了,今天的内容就差不多了。

one more thing,还是关于命名空间的一些“技巧”,我们也可以在 Apple 公司新推出的 Combine 框架里看到,例如用 enum 做 namespace:传送门