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

相關文章
相關標籤/搜索