cv.cm

編碼 · 程式語言

Swift 入門教學

閱讀約 9 分鐘

簡答

Swift 是 Apple 為 iPhone、iPad、Mac、Apple Watch 與 Apple TV 應用程式打造的快速、安全的編譯式語言,也能在 Linux 與 Windows 上執行。將 print("Hello, world") 存成 hello.swift,再執行 swift hello.swift。核心概念包括 let 常數與 var 變數、型別推論、用來表示可能缺少之值的 Optional、具備值語意的 struct 與 enum、protocol,以及以 do/try/catch 處理的錯誤。

開啟JSON 格式化

Swift 是什麼,用來做什麼

Swift 由 Apple 於 2014 年推出,作為 Objective-C 的現代替代品,並於 2015 年開放原始碼;專案位於 swift.org。它是 Apple 平台應用程式的主要語言,通常搭配 SwiftUI 或 UIKit 框架。Swift 也用於伺服器端程式(例如搭配 Vapor 框架)和命令列工具。這門語言的設計目的是預防常見錯誤:變數必須初始化、陣列會檢查邊界,而 nil 必須被明確處理。

安裝 Swift 並執行 Hello, world

在 Mac 上,從 App Store 安裝 Xcode,或安裝 Xcode 命令列工具。在 Linux 與 Windows 上,則從 swift.org 下載工具鏈。用 swift --version 確認。

print("Hello, world")
swift hello.swift
# Hello, world
swiftc hello.swift -o hello   # compile to a binary
./hello

較大的專案請執行 swift package init --type executable,再執行 swift run。Xcode playground 也是方便實驗的方式。

常數、變數與基本型別

常數用 let,變數用 var。優先使用 let;當 var 從未被修改時,編譯器會發出警告。型別會被推論,且 Swift 絕不會在數值型別之間隱含轉換。

型別範例說明
Intlet n = 42在現代平台上為 64 位元
Doublelet x = 3.14預設的浮點數型別
Boollet ok = truetrue 或 false
Stringlet s = "hi \(name)"Unicode 文字,值型別
Characterlet c: Character = "é"一個使用者感知的字元
[Int], [String: Int][1, 2], ["a": 1]陣列與字典
let name = "Ada"
var count = 7
count += 1
let avg = Double(count) / 2   // explicit conversion
print("\(name) has \(count) items, avg \(avg)")  // Ada has 8 items, avg 4.0
print(7 / 2, 7 % 2)          // 3 1

字串字面值中的 \(expression) 會插入一個值(字串內插)。

流程控制

let score = 72
if score >= 90 {
    print("A")
} else if score >= 70 {
    print("B")                 // B
} else {
    print("C")
}

for i in 0..<3 {
    print(i, terminator: " ")  // 0 1 2
}
print()

var n = 10
while n > 1 { n /= 2 }
print(n)                       // 1

let day = "SAT"
switch day {
case "SAT", "SUN":
    print("weekend")           // weekend
default:
    print("weekday")
}

switch 必須窮盡所有情況,預設不會繼續往下執行,並且可以比對範圍(case 90...100:)、tuple 和 enum case。

函式與閉包

參數帶有呼叫端要寫出的引數標籤,讓呼叫讀起來像一句話。用 _ 可以省略標籤。閉包是以大括號寫成的匿名函式。

func greet(_ name: String, with greeting: String = "Hello") -> String {
    return "\(greeting), \(name)!"
}

print(greet("Lin"))                  // Hello, Lin!
print(greet("Lin", with: "Hi"))      // Hi, Lin!

let nums = [3, 1, 2, 5]
let squares = nums.map { $0 * $0 }
let evens = nums.filter { $0 % 2 == 0 }
let sorted = nums.sorted(by: >)
print(squares, evens, sorted)        // [9, 1, 4, 25] [2] [5, 3, 2, 1]

$0 代表閉包的第一個引數。當閉包是最後一個引數時,可以寫在括號之後(尾隨閉包語法)。

陣列、字典與集合

var fruits = ["apple", "pear"]
fruits.append("fig")
print(fruits.count, fruits[0], fruits.contains("fig"))  // 3 apple true

var ages: [String: Int] = ["ann": 31, "bob": 25]
ages["cy"] = 40
for (person, age) in ages.sorted(by: { $0.key < $1.key }) {
    print(person, age)                                  // ann 31, bob 25, cy 40
}
print(ages["zed", default: 0])                          // 0

let tags: Set = ["b", "a", "b"]
print(tags.count)                                       // 2

集合是值型別:把陣列指派給另一個變數會建立獨立的副本(以延遲方式複製,只有在其中一個被修改時才會真正複製)。以 let 宣告的陣列則完全不能修改。

招牌特色:Optional

可能缺少的值具有 Optional 型別,寫成 String?。在解包(unwrap)之前,你不能把它當作 String 使用,所以編譯器會強制你處理 nil 的情況。

let input = "42"
let number: Int? = Int(input)      // nil if input is not a number

if let number {                     // unwrap: runs only when not nil
    print("got \(number)")          // got 42
}

func firstWord(_ text: String) -> String? {
    text.split(separator: " ").first.map(String.init)
}

guard let word = firstWord("hello world") else {
    fatalError("no words")
}
print(word)                                  // hello
print(firstWord("")?.uppercased() ?? "none") // none

if let 與 guard let 能安全地解包,?.(Optional 串連)只在值存在時才呼叫方法,?? 則提供預設值。強制解包 value! 在值為 nil 時會當機,所以應少用。簡寫的 if let number 需要 Swift 5.7 以上。

Struct、class、enum 與 protocol

struct 與 enum 是值型別;class 是支援繼承的參考型別。Swift 程式碼偏好使用 struct 與 protocol(介面),而不是 class 繼承階層。

protocol Shape {
    func area() -> Double
}

struct Rect: Shape {
    var width: Double
    var height: Double
    func area() -> Double { width * height }
}

enum Circle: Shape {
    case unit
    case radius(Double)
    func area() -> Double {
        switch self {
        case .unit: return Double.pi
        case .radius(let r): return Double.pi * r * r
        }
    }
}

let shapes: [any Shape] = [Rect(width: 2, height: 3), Circle.radius(1)]
let total = shapes.reduce(0) { $0 + $1.area() }
print((total * 100).rounded() / 100)   // 9.14

struct 會自動取得成員逐一初始化方法(Rect(width:height:))。enum 的 case 可以攜帶關聯值,如 .radius(Double)。

錯誤處理

可能失敗的函式會標示 throws。呼叫端必須在 do/catch 區塊內使用 try,或使用 try? 把錯誤轉成 nil。

enum AgeError: Error {
    case notANumber(String)
    case negative
}

func parseAge(_ text: String) throws -> Int {
    guard let age = Int(text) else { throw AgeError.notANumber(text) }
    guard age >= 0 else { throw AgeError.negative }
    return age
}

do {
    print(try parseAge("41"))          // 41
    print(try parseAge("abc"))
} catch AgeError.notANumber(let text) {
    print("not a number: \(text)")     // not a number: abc
} catch {
    print("other error: \(error)")
}

let maybe = try? parseAge("-3")        // nil
print(maybe as Any)                    // nil

完整的小程式:單字頻率統計

import Foundation

let args = CommandLine.arguments
guard args.count == 2 else {
    print("usage: swift words.swift FILE")
    exit(1)
}

do {
    let text = try String(contentsOfFile: args[1], encoding: .utf8).lowercased()
    let words = text.split { !$0.isLetter && $0 != "'" }
    var counts: [Substring: Int] = [:]
    for word in words {
        counts[word, default: 0] += 1
    }
    for (word, n) in counts.sorted(by: { $0.value > $1.value }).prefix(5) {
        print(String(n).padding(toLength: 6, withPad: " ", startingAt: 0) + word)
    }
} catch {
    print("cannot read \(args[1]): \(error.localizedDescription)")
    exit(1)
}

以 swift words.swift notes.txt 執行。Foundation 提供讀取檔案與字串輔助功能;它在 Apple 平台以及 Linux 與 Windows 的工具鏈中都可使用。

初學者常見錯誤

  • 試圖修改 let 常數,或以 let 宣告的 struct 的屬性。值需要改變時請使用 var。
  • 以為 struct 會像物件一樣被共用。修改副本不會改變原本的值;如果需要共用同一個實體,請使用 class。
  • 用整數對 String 做索引,例如 s[0]。請使用 s.first、s.prefix(3) 或 String.Index 值。
  • 在算術中混用 Int 和 Double 而不轉換,這會造成編譯錯誤。
  • 在會拋出錯誤的呼叫前忘了寫 try,或使用 try!,後者在出錯時會當機。

接下來學什麼

閱讀 docs.swift.org 上的「The Swift Programming Language」,這是官方的指南與參考,以及 swift.org 上的文件中心。若要開發應用程式,請在 Xcode 中跟著 Apple 的 SwiftUI 教學學習。接著學習 Swift 並行(async/await 與 actor)、泛型,以及管理相依套件的 Swift Package Manager。

常見問題

Swift 只能用於 Apple 平台嗎?

不是。Swift 是開放原始碼的,也能在 Linux 與 Windows 上執行,並用於伺服器端程式與工具。不過 SwiftUI 與 UIKit 等應用程式框架僅限 Apple 平台。

該學 Swift 還是 Objective-C?

學 Swift。它是 Apple 為新應用程式推薦的語言,需要時也能呼叫既有的 Objective-C 程式碼。

Swift 中的 struct 和 class 有什麼差別?

struct 是值型別,賦值或傳遞時會被複製;class 是參考型別,所有變數共用同一個實體。class 還支援繼承與解構子(deinitializer)。

型別後面的問號是什麼意思?

Int? 是 Optional:它存放的不是 Int 就是 nil。使用該值之前必須先解包,例如用 if let。

全部編碼教學