作者:Mattt, 原文链接 ,原文日期:2019-01-07 译者: 雨谨 ;校对: numbbbbb , Yousanflics ;定稿: Pancf
作为软件开发人员,我们学到的第一课是如何将概念和功能组织成独立的单元。在最小的层级上,这意味着思考类型、方法和属性。这些东西构成了模块(module)的基础,而模块又可以被打包成为 library 或者 framework。
在这种方式中,import 声明是将所有内容组合在一起的粘合剂。
尽管 import 声明非常重要,但大部分 Swift 开发者都只熟悉它的最基本用法:
import <#module#>
复制代码
本周的 NSHipster 中,我们将探索 Swift 这个最重要的功能的其他用法。
import 声明允许你的代码访问其他文件中声明的符号。但是,如果多个模块都声明了一个同名的函数或类型,那么编译器将无法判断你的代码到底想调用哪个。
为了演示这个问题,考虑 铁人三项(Triathlon) 和 铁人五项(Pentathlon) 这两个代表多运动比赛的模块:
铁人三项 包括三个项目:游泳、自行车和跑步。
// 铁人三项模块
func swim() {
print("🏊 Swim 1.5 km")
}
func bike() {
print("🚴 Cycle 40 km")
}
func run() {
print("🏃 Run 10 km")
}
复制代码
铁人五项 模块由五个项目组成:击剑、游泳、马术、射击和跑步。
// 铁人五项模块
func fence() {
print("🤺 Bout with épées")
}
func swim() {
print("🏊 Swim 200 m")
}
func ride() {
print("🏇 Complete a show jumping course")
}
func shoot() {
print("🎯 Shoot 5 targets")
}
func run() {
print("🏃 Run 3 km cross-country")
}
复制代码
如果我们单独 import 其中一个模块,我们可以通过它们的 非限定(unqualified) 名称引用它们的每个函数,而不会出现问题。
import Triathlon
swim() // 正确,调用 Triathlon.swim
bike() // 正确,调用 Triathlon.bike
run() // 正确,调用 Triathlon.run
复制代码
但是如果同时 import 两个模块,我们不能全部使用非限定函数名。铁人三项和五项都包括游泳和跑步,所以对
swim()
的引用是模糊的。
import Triathlon
import Pentathlon
bike() // 正确,调用 Triathlon.bike
fence() // 正确,调用 Pentathlon.fence
swim() // 错误,模糊不清
复制代码
如何解决这个问题?一种策略是使用 全限定名称(fully-qualified name) 来处理任何不明确的引用。通过包含模块名称,程序是要在游泳池中游几圈,还是在开放水域中游一英里,就不存在混淆了。
import Triathlon
import Pentathlon
Triathlon.swim() // 正确,指向 Triathlon.swim 的全限定引用
Pentathlon.swim() // 正确,指向 Pentathlon.swim 的全限定引用
复制代码
解决 API 名称冲突的另一种方法是更改 import 声明,使其更加严格地挑选需要包含每个模块哪些的内容。
import 单个声明
import 声明提供了一种样式,可以指定引入定义在顶层(top-level)的单个结构体、类、枚举、协议和类型别名,以及函数、常量和变量。
import <#kind#> <#module.symbol#>
复制代码
这里,
<#kind#>
可以为如下的任何关键字:
Kind | Description |
---|---|
struct
|
结构体 |
class
|
类 |
enum
|
枚举 |
protocol
|
协议 |
typealias
|
类型别名 |
func
|
函数 |
let
|
常量 |
var
|
变量 |
例如,下面的 import 声明只添加了
Pentathlon
模块的
swim()
函数:
import func Pentathlon.swim
swim() // 正确,调用 Pentathlon.swim
fence() // 错误,无法解析的标识
复制代码
解决符号名称冲突
当代码中多个符号被同一个名字被引用时,Swift 编译器参考以下信息,按优先级顺序解析该引用:
- 本地的声明
- 单个导入(import)的声明
- 整体导入的模块
如果任何一个优先级有多个候选项,Swift 将无法解决歧义,进而引发编译错误。
例如,整体导入的
Triathlon
模块会提供
swim()
、
bike()
和
run()
方法,但从
Pentathlon
中单个导入的
swim()
函数声明会覆盖
Triathlon
模块中的对应函数。同样,本地声明的
run()
函数会覆盖
Triathlon
中的同名符号,也会覆盖任何单个导入的函数声明。
import Triathlon
import func Pentathlon.swim
// 本地的函数会遮住整体导入的 Triathlon 模块
func run() {
print("🏃 Run 42.195 km")
}
swim() // 正确,调用 Pentathlon.swim
bike() // 正确,调用 Triathlon.bike
run() // 正确,调用本地的 run
复制代码
那这个代码的运行结果是?一个古怪的多运动比赛,包括在一个泳池里游几圈的游泳,一个适度的自行车骑行,和一个马拉松跑。 (@ 我们, 钢铁侠)
如果本地或者导入的声明,与模块的名字发生冲突,编译器首先查找声明,然后在模块中进行限定查找。
import Triathlon
enum Triathlon { case sprint, olympic, ironman }
Triathlon.olympic // 引用本地的枚举 case Triathlon.swim() // 引用模块的函数
Swift编译器不会通知开发者,也无法协调模块和本地声明之间的命名冲突,因此使用依赖项时,你应该了解这种可能性。
澄清和缩小范围
除了解决命名冲突之外,import 声明还可以作为澄清程序员意图的一种方法。
例如,如果只使用 AppKit 这样大型框架中的一个函数,那么你可以在 import 声明中单独指定这个函数。
import func AppKit.NSUserName
NSUserName() // "jappleseed"
复制代码
顶层常量和变量的来源通常比其他的导入符号更难识别,在导入它们时,这个技术尤其有用。