Swift 模組與引用 (Modules & Imports)
在大型專案開發中,了解模組 (Module) 的概念至關重要。Swift 使用模組來組織程式碼、管理存取權限並實現程式碼重用。
什麼是模組 (Module)?
模組是 Swift 程式碼分發 (Code Distribution) 的單位。
- 一個 Framework (如 SwiftUI, UIKit, Foundation)。
- 一個 Application (你的 App Target)。
- 一個 Swift Package 的 Library。
每個模組都有自己的命名空間 (Namespace),這意味著不同模組可以有相同名稱的類別或函式,而不會產生衝突。
引入語法 (import)
最基本的使用方式是引入整個模組:
import SwiftUI
import CoreLocation
這會將該模組中所有 public 和 open 的 API 暴露給當前檔案。
引入特定符號 (Importing Specific Symbols)
如果你只想要引入模組中的某一個特定功能,可以使用特定的關鍵字。這在大型模組中可以讓依賴關係更清晰。
語法格式:import [kind] [module].[symbol]
| 關鍵字 | 用途 | 範例 |
|---|---|---|
class | 引入類別 | import class UIKit.UIButton |
struct | 引入結構 | import struct SwiftUI.Text |
func | 引入函式 | import func Darwin.sqrt |
enum | 引入列舉 | import enum Combine.Publishers |
protocol | 引入協定 | import protocol Swift.Codable |
var | 引入變數 | import var Swift.dump |
這樣做的好處是只引入你需要的。例如,你只想要使用 Darwin 中的 sqrt 函式,而不想要其他的數學符號污染你的全域命名空間:
import func Darwin.sqrt
let result = sqrt(4.0)
// let p = pow(2.0, 3.0) // 錯誤:pow 沒有被引入
處理名稱衝突 (Name Conflicts)
當兩個不同的模組擁有相同名稱的型別時,就會發生衝突。
例如,假設你有兩個 Framework:DrawingKit 和 GeometryKit,它們都有一個 Point 結構。
import DrawingKit
import GeometryKit
// 錯誤:編譯器不知道你要用哪一個 Point
// let p = Point(x: 0, y: 0)
解決方案 1:使用全名 (Fully Qualified Name)
直接加上模組名稱作為前綴:
let p1 = DrawingKit.Point(x: 0, y: 0)
let p2 = GeometryKit.Point(x: 0, y: 0)
解決方案 2:使用 typealias 重新命名
如果你不想每次都打全名,可以在檔案開頭取個彆名:
import DrawingKit
import GeometryKit
typealias DrawPoint = DrawingKit.Point
typealias GeoPoint = GeometryKit.Point
let p1 = DrawPoint(x: 0, y: 0)
常見的真實案例:Image
SwiftUI 和 UIKit 都有 Image 相關的型別,雖然名稱不完全一樣 (Image vs UIImage),但有時候我們需要明確轉換:
import SwiftUI
import UIKit // 為了使用 UIImage
struct ContentView: View {
var body: some View {
// 使用 SwiftUI 的 Image,初始化時傳入 UIKit 的 UIImage
SwiftUI.Image(uiImage: UIKit.UIImage(named: "icon")!)
}
}
進階:重新匯出 (@_exported)
這是一個 Swift 的隱藏特性 (雖然尚未正式標準化,但在許多開源庫中被廣泛使用)。
當你在模組 A 中 import 模組 B 時,預設情況下,使用模組 A 的人看不見模組 B 的內容。
如果你希望使用者只要 import A,就能自動獲得 import B 的效果,可以使用 @_exported:
// MyCustomKit.swift
@_exported import SwiftUI
// 這樣一來,只要其他檔案 import MyCustomKit,
// 就會自動擁有 SwiftUI 的所有功能,不需要再寫 import SwiftUI
這常用於建立「雨傘框架 (Umbrella Framework)」,將多個小 Library 包裝成一個大 Library 方便使用。
單元測試專用 (@testable)
當我們在寫 Unit Test 時,專案結構通常分為:
- App Target (主程式)
- Test Target (測試程式) —— 這是一個獨立的模組!
根據存取控制規則,Test Target 只能存取 App Target 中 public 的成員。但我們通常想測試 internal 的邏輯。
這時需要使用 @testable import:
// MyTests.swift
import XCTest
@testable import MyApp // 關鍵!
class MyTests: XCTestCase {
func testInternalLogic() {
// 即使 User 是 internal 的,這裡也能存取
let user = User(name: "Test")
XCTAssertEqual(user.name, "Test")
}
}
它會暫時提升模組間的權限,讓測試目標可以看見並存取原本是 internal 的內容。
總結
- Module 是程式碼分發的單位。
- 使用 Specific Import (如
import class) 可以避免汙染命名空間。 - 遇到 名稱衝突 時,使用模組名稱前綴 (Module.Type) 或
typealias解決。 @testable import是 Unit Test 的必備工具。