做者:Mattt,原文連接,原文日期:2019-01-07 譯者:雨謹;校對:numbbbbb,Yousanflics;定稿: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 聲明提供了一種樣式,能夠指定引入定義在頂層(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 編譯器參考如下信息,按優先級順序解析該引用:
若是任何一個優先級有多個候選項,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 聲明樣式,提供了另外一種限制 API 暴露的方式。
import <#module.submodule#>
複製代碼
你極可能在 AppKit 和 Accelerate 等大型的系統 framework 中遇到子模塊。雖然這種 傘架構(umbrella framework) 再也不是一種最佳實踐,但它們在 20 世紀初蘋果向 Cocoa 過渡的過程當中發揮了重要做用。
例如,你能夠僅 import Core Services framework 的 DictionaryServices 子模塊,從而將你的代碼與無數已廢棄的 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。