
swift-language
熱門針對非並發、非 SwiftUI 的程式碼,套用現代 Swift 語言模式與慣用寫法。涵蓋 if/switch 表達式(Swift 5.9+)、型別化拋錯(Swift 6+)、結果建構器、屬性包裝器、不透明與存在型別(some vs any)、guard 模式、Never 型別、Regex 建構器(Swift 5.7+)、基本 Codable 塑形(CodingKeys、自訂解碼、巢狀容器)、現代集合 API(count(where:)、contains(where:)、replacing())、基本 FormatStyle 用法及字串插值模式。適用於撰寫涉及泛型、協定、列舉、閉包或現代語言特性的核心 Swift 程式碼;深度 Codable 請導向 swift-codable,詳細格式化/本地化請導向 swift-formatstyle,API 命名請導向 swift-api-design-guidelines。
針對非並發、非 SwiftUI 的程式碼,套用現代 Swift 語言模式與慣用寫法。涵蓋 if/switch 表達式(Swift 5.9+)、型別化拋錯(Swift 6+)、結果建構器、屬性包裝器、不透明與存在型別(some vs any)、guard 模式、Never 型別、Regex 建構器(Swift 5.7+)、基本 Codable 塑形(CodingKeys、自訂解碼、巢狀容器)、現代集合 API(count(where:)、contains(where:)、replacing())、基本 FormatStyle 用法及字串插值模式。適用於撰寫涉及泛型、協定、列舉、閉包或現代語言特性的核心 Swift 程式碼;深度 Codable 請導向 swift-codable,詳細格式化/本地化請導向 swift-formatstyle,API 命名請導向 swift-api-design-guidelines。
Swift 語言模式
套用當前 Swift 語言語法,不改變行為或求值順序。
深度解碼請導向 swift-codable,格式化請導向 swift-formatstyle,命名請導向 swift-api-design-guidelines,並發請導向 swift-concurrency,SwiftUI 狀態/檢視工作請導向 swiftui-patterns。
目錄
- If/Switch 表達式
- 型別化拋錯
- 結果建構器
- 屬性包裝器
- 不透明與存在型別
- Guard 模式
- Never 型別
- Regex 建構器
- Codable 最佳實踐
- 現代集合 API
- FormatStyle
- 字串插值
- 常見錯誤
- 審查清單
- 參考資料
If/Switch 表達式
現代化時,鎖定當前行為與求值順序,進行一次語意改寫,編譯受影響的模組,並執行聚焦的測試案例。修正任何變更後再繼續,重複直到行為保持一致。
Swift 5.9+ 允許 if 和 switch 作為回傳值的表達式。可用於直接賦值、回傳或初始化。
// 從 if 表達式賦值
let icon = if isComplete { "checkmark.circle.fill" } else { "circle" }
// 從 switch 表達式賦值
let label = switch status {
case .draft: "Draft"
case .published: "Published"
case .archived: "Archived"
}
// 用於回傳位置
func badgeText(for priority: Priority) -> String {
switch priority {
case .high: "High"
case .medium: "Medium"
case .low: "Low"
}
}
規則:
- 每個分支必須產生相同型別的值。
- 不允許多陳述式分支——每個分支為單一表達式。
- 用作函式引數時,用括號包裹以避免歧義。
型別化拋錯
Swift 6+ 允許指定函式拋出的錯誤型別。
enum ValidationError: Error {
case tooShort, invalidCharacters, alreadyTaken
}
func validate(username: String) throws(ValidationError) -> String {
guard username.count >= 3 else { throw .tooShort }
guard username.allSatisfy(\.isLetterOrDigit) else { throw .invalidCharacters }
return username.lowercased()
}
// 呼叫端取得型別化錯誤——無需轉型
do {
let name = try validate(username: input)
} catch {
// error 是 ValidationError,而非 any Error
switch error {
case .tooShort: print("太短")
case .invalidCharacters: print("無效字元")
case .alreadyTaken: print("已被使用")
}
}
規則:
- 僅在呼叫端需要窮舉錯誤處理時使用
throws(SomeError)。對於混合錯誤來源,使用無型別throws。 - 現代化一個僅使用單一區域錯誤列舉的輔助函式時,偏好
throws(ErrorEnum)並註明 Swift 6+。 throws(Never)標記一個語法上會拋錯但實際上從不拋錯的函式——在泛型情境中很有用。- 型別化拋錯會傳遞:一個呼叫
throws(A)和throws(B)的函式,其自身必須拋出涵蓋兩者的型別(或使用無型別throws)。
結果建構器
@resultBuilder 啟用 DSL 風格的語法。SwiftUI 的 @ViewBuilder 是最常見的例子,但你可以為任何領域建立自訂建構器。
@resultBuilder
struct ArrayBuilder<Element> {
static func buildBlock(_ components: [Element]...) -> [Element] {
components.flatMap { $0 }
}
static func buildExpression(_ expression: Element) -> [Element] { [expression] }
static func buildOptional(_ component: [Element]?) -> [Element] { component ?? [] }
static func buildEither(first component: [Element]) -> [Element] { component }
static func buildEither(second component: [Element]) -> [Element] { component }
static func buildArray(_ components: [[Element]]) -> [Element] { components.flatMap { $0 } }
}
func makeItems(@ArrayBuilder<String> content: () -> [String]) -> [String] { content() }
let items = makeItems {
"Always included"
if showExtra { "Conditional" }
for name in names { name.uppercased() }
}
建構器方法: buildBlock(組合陳述式)、buildExpression(單一值)、buildOptional(if 無 else)、buildEither(if/else)、buildArray(for..in)、buildFinalResult(可選的後處理)。
屬性包裝器
自訂 @propertyWrapper 型別封裝儲存與存取模式。
@propertyWrapper
struct Clamped<Value: Comparable> {
private var value: Value
let range: ClosedRange<Value>
var wrappedValue: Value {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
var projectedValue: ClosedRange<Value> { range }
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
// 使用方式
struct Volume {
@Clamped(0...100) var level: Int = 50
}
var v = Volume()
v.level = 150 // 限制為 100
print(v.$level) // 投影值:0...100
設計規則:
wrappedValue是主要的 getter/setter。projectedValue(透過$property存取)提供中繼資料或綁定。- 屬性包裝器可以組合:
@A @B var x先套用外層包裝器。 - 當簡單的計算屬性就足夠時,不要使用屬性包裝器。
不透明與存在型別
some Protocol(不透明型別)
呼叫端不知道具體型別,但編譯器知道。-> some P 的回傳在所有回傳分支中只有一個固定的底層具體型別。
func makeCollection() -> some Collection<Int> {
[1, 2, 3] // 總是回傳 Array<Int>——編譯器知道具體型別
}
使用 some 的時機:
- 回傳型別:想隱藏實作但保留型別識別。
- 參數型別(Swift 5.7+):
some P是未命名泛型參數(如<T: P>)的簡寫。
any Protocol(存在型別)
一個存在性盒子,可在執行時容納任何符合的型別。它使用動態派發,且當值無法放入內聯緩衝區時可能分配記憶體。
func process(items: [any StringProtocol]) {
for item in items {
print(item.uppercased())
}
}
何時選擇
使用 some |
使用 any |
|---|---|
| 回傳型別隱藏具體型別 | 異質集合 |
| 函式參數(取代簡單泛型) | 需要動態型別抹消 |
| 較佳效能(靜態派發) | 協定有 Self 或關聯型別需求需抹消 |
經驗法則: 預設使用 some。僅在需要異質集合或執行時型別彈性時使用 any。
Guard 模式
guard 強制執行前置條件並允許提前退出。它讓快樂路徑保持靠左,減少巢狀層級。
func processOrder(_ order: Order?) throws -> Receipt {
// 解開可選值
guard let order else { throw OrderError.missing }
// 驗證條件
guard order.items.isEmpty == false else { throw OrderError.empty }
guard order.total > 0 else { throw OrderError.invalidTotal }
// 布林檢查
guard order.isPaid else { throw OrderError.unpaid }
// 模式匹配
guard case .confirmed(let date) = order.status else {
throw OrderError.notConfirmed
}
return Receipt(order: order, confirmedAt: date)
}
最佳實踐:
- 使用
guard處理前置條件,if處理分支邏輯。 - 合併相關的 guard:
guard let a, let b else { return }。 else區塊必須離開作用域:return、throw、continue、break或fatalError()。- 使用簡寫解開:
guard let value else { ... }(Swift 5.7+)。
Never 型別
Never 是一個無人居住的型別,用於從不產生值的程式碼路徑。它僅在值表達式可用或可推斷時表現得像 Swift 的底層型別;它不是通用的型別見證,不會隱式符合任意協定,也無法滿足泛型約束(如 T: P),除非該約束對 Never 本身有效。
// 終止程式的函式
func crashWithDiagnostics(_ message: String) -> Never {
let diagnostics = gatherDiagnostics()
logger.critical("\(message): \(diagnostics)")
fatalError(message)
}
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
// Result<String, Never>——一個永遠不會失敗的結果
// 窮舉 switch:無需 default,因為 Never 沒有 case
func handle(_ result: Result<String, Never>) {
switch result {
case .success(let value): print(value)
// 不需要 .failure case——編譯器知道不可能
}
}
Regex 建構器
Swift 5.7+ 的 Regex 建構器 DSL 提供編譯時檢查、可讀性高的模式。
import Foundation
import RegexBuilder
// 將 "2024-03-15" 解析為元件
let dateRegex = Regex {
Capture { /\d{4}/ }; "-"; Capture { /\d{2}/ }; "-"; Capture { /\d{2}/ }
}
if let match = "2024-03-15".firstMatch(of: dateRegex) {
let (_, year, month, day) = match.output
_ = (year, month, day)
}
// TryCapture 搭配轉換
let priceRegex = Regex {
"$"
TryCapture { OneOrMore(.digit); "."; Repeat(.digit, count: 2) }
transform: { Decimal(string: String($0)) }
}
何時使用建構器 vs. 字面量:
- 建構器:複雜模式、可重用元件、對捕獲的強型別。
- 字面量(
/pattern/):簡單模式、熟悉正則語法。 - 兩者可混合使用:在建構器區塊中嵌入
/.../字面量。
Codable 最佳實踐
使用 CodingKeys 進行簡單重新命名,僅在實際酬載形狀或轉換不匹配時使用自訂解碼。載入擴展 Swift 模式以獲得簡潔的語言範例;實作與驗證請使用 swift-codable。
現代集合 API
偏好這些現代 API 而非手動迴圈:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8]
// count(where:)——取代 .filter { }.count
let evenCount = numbers.count(where: { $0.isMultiple(of: 2) })
// contains(where:)——在第一個匹配時短路
let hasNegative = numbers.contains(where: { $0 < 0 })
// first(where:) / last(where:)
let firstEven = numbers.first(where: { $0.isMultiple(of: 2) })
// String replacing()——Swift 5.7+,回傳新字串
let cleaned = rawText.replacing(/\s+/, with: " ")
let snakeCase = name.replacing("_", with: " ")
// compactMap——從轉換中解開可選值
let ids = strings.compactMap { Int($0) }
// flatMap——扁平化巢狀集合
let allTags = articles.flatMap(\.tags)
// Dictionary(grouping:by:)
let byCategory = Dictionary(grouping: items, by: \.category)
// reduce(into:)——高效累積
let freq = words.reduce(into: [:]) { counts, word in
counts[word, default: 0] += 1
}
FormatStyle
使用 .formatted() 和 Text(_:format:) 進行基本顯示。樣式選擇、解析、本地化測試及可重用格式化器設計請導向 swift-formatstyle。
字串插值
擴展 DefaultStringInterpolation 以進行領域特定格式化。使用 """ 處理多行字串(縮排相對於結尾的 """)。自訂插值範例請參閱 references/swift-patterns-extended.md。
常見錯誤
- 在
some可行時使用any。 回傳型別和參數預設使用some,但每個-> some P分支必須回傳相同的具體型別。 - 手動迴圈或
.filter { }.count而非集合 API。 使用count(where:)進行條件計數,以及contains(where:)、compactMap、flatMap取代額外迭代或陣列。 - 使用
DateFormatter而非 FormatStyle。.formatted()更簡單、型別安全,且自動處理本地化。 - 強制解開 Codable 解碼。 對可選或缺失的鍵使用
decodeIfPresent搭配預設值。 - 現代化時重新排序前置條件。 使用
guard時,不要在驗證前移動正規化或轉換。 - 無效的
@c簽名。 說明UnsafeBufferPointer是 Swift 結構/值包裝器,然後拒絕String、Array、閉包和泛型佔位符。 - 忽略型別化拋錯。 當函式有單一明確的錯誤型別時,型別化拋錯讓呼叫端無需轉型即可進行窮舉 switch。
- 過度使用屬性包裝器。 當沒有重用或投影值需求時,計算屬性更簡單。
- 對
Never的規格不足。 對於Result<T, Never>或throws(Never),明確寫出注意事項:Never 不會隱式符合任意協定,無法滿足任意T: P約束,且僅在有效的表達式/推斷情境中表現得像底層型別。 - 擁有兄弟技能的實作。 標明擁有者技能並停止。避免提供
CodingKeys、解碼器、格式化器、SwiftUI 或並發的程式碼片段。
審查清單
- [ ]
some僅在每個不透明回傳分支具有一個具體型別時使用 - [ ]
guard用於前置條件;count(where:)取代手動計數或.filter { }.count - [ ] 使用
.formatted()而非DateFormatter/NumberFormatter - [ ] Codable 型別使用
CodingKeys進行 API 映射;對可選欄位使用decodeIfPresent搭配預設值 - [ ] if/switch 表達式用於條件賦值;屬性包裝器具備明確的重用理由
- [ ] 複雜模式使用 Regex 建構器(簡單模式可使用字面量)
- [ ] 型別化拋錯用於單一區域錯誤領域,並註明 Swift 6+ 相容性
- [ ]
@c修正說明UnsafeBufferPointer是 Swift 結構/值包裝器,並按名稱列舉被拒絕的純 Swift 型別 - [ ]
Never指引使用「無人居住」和「底層型別」,並說明無隱式任意協定/泛型符合 - [ ] 深度 Codable 導向
swift-codable;FormatStyle API 導向swift-formatstyle;市場/本地化顯示 QA 導向ios-localization;命名/並發/SwiftUI 導向兄弟技能
參考資料
- 擴展模式與 Codable 範例:references/swift-patterns-extended.md
- 屬性與 C 互操作:references/swift-attributes-interop.md



