Swift Import 声明

做者:Mattt,原文连接,原文日期:2019-01-07 译者:雨谨;校对:numbbbbbYousanflics;定稿:Pancfhtml

做为软件开发人员,咱们学到的第一课是如何将概念和功能组织成独立的单元。在最小的层级上,这意味着思考类型、方法和属性。这些东西构成了模块(module)的基础,而模块又能够被打包成为 library 或者 framework。git

在这种方式中,import 声明是将全部内容组合在一块儿的粘合剂。程序员

尽管 import 声明很是重要,但大部分 Swift 开发者都只熟悉它的最基本用法:github

import <#module#>
复制代码

本周的 NSHipster 中,咱们将探索 Swift 这个最重要的功能的其余用法。swift


import 声明容许你的代码访问其余文件中声明的符号。可是,若是多个模块都声明了一个同名的函数或类型,那么编译器将没法判断你的代码到底想调用哪一个。架构

为了演示这个问题,考虑 铁人三项(Triathlon)铁人五项(Pentathlon) 这两个表明多运动比赛的模块:app

铁人三项 包括三个项目:游泳、自行车和跑步。框架

// 铁人三项模块
func swim() {
    print("🏊‍ Swim 1.5 km")
}

func bike() {
    print("🚴 Cycle 40 km")
}

func run() {
    print("🏃‍ Run 10 km")
}
复制代码

铁人五项 模块由五个项目组成:击剑、游泳、马术、射击和跑步。ide

// 铁人五项模块
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 编译器参考如下信息,按优先级顺序解析该引用:

  1. 本地的声明
  2. 单个导入(import)的声明
  3. 总体导入的模块

若是任何一个优先级有多个候选项,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"
复制代码

顶层常量和变量的来源一般比其余的导入符号更难识别,在导入它们时,这个技术尤为有用。

例如,Darwin framework 提供的众多功能中,包含一个顶层的 stderr 变量。这里的一个显式 import 声明能够在代码评审时,提早避免该变量来源的任何疑问。

import func Darwin.fputs
import var Darwin.stderr

struct StderrOutputStream: TextOutputStream {
    mutating func write(_ string: String) {
        fputs(string, stderr)
    }
}

var standardError = StderrOutputStream()
print("Error!", to: &standardError)
复制代码

import 子模块

最后一种 import 声明样式,提供了另外一种限制 API 暴露的方式。

import <#module.submodule#>
复制代码

你极可能在 AppKit 和 Accelerate 等大型的系统 framework 中遇到子模块。虽然这种 伞架构(umbrella framework) 再也不是一种最佳实践,但它们在 20 世纪初苹果向 Cocoa 过渡的过程当中发挥了重要做用。

例如,你能够仅 import Core Services frameworkDictionaryServices 子模块,从而将你的代码与无数已废弃的 API(如 Carbon Core)隔离开来。

import Foundation
import CoreServices.DictionaryServices

func define(_ word: String) -> String? {
    let nsstring = word as NSString
    let cfrange = CFRange(location: 0, length: nsstring.length)

    guard let definition = DCSCopyTextDefinition(nil, nsstring, cfrange) else {
        return nil
    }

    return String(definition.takeUnretainedValue())
}

define("apple") // "apple | ˈapəl | noun 1 the round fruit of a tree..."
复制代码

事实上,单独导入的声明和子模块,除了澄清程序员的意图,并不能带来任何真正的好处。这种方式并不会让你的代码编译地更快。因为大部分的子模块彷佛都会从新导入它们的伞头文件(umbrella header),所以这种方式也无法减小自动补全列表上的噪音。


与许多晦涩难懂的高级主题同样,你之因此没有据说过这些 import 声明样式,极可能的是由于你不须要了解它们。若是你已经在没有它们的状况下开发了不少 APP,那么你彻底有理由能够相信,你不须要开始使用它们。

相反,这里比较有价值的收获是理解 Swift 编译器如何解决命名冲突。为此,理解 import 声明是很是重要的。

本文由 SwiftGG 翻译组翻译,已经得到做者翻译受权,最新文章请访问 swift.gg

相关文章
相关标签/搜索