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 变量、类型推断、用于可能缺失的值的可选类型、具有值语义的结构体和枚举、协议,以及用 do/try/catch 处理的错误。

打开JSON 格式化

Swift 是什么,用来做什么

Swift 由 Apple 于 2014 年推出,作为 Objective-C 的现代替代品,并于 2015 年开源;项目位于 swift.org。它是 Apple 平台应用的主要开发语言,通常搭配 SwiftUI 或 UIKit 框架。Swift 也用于服务器端代码(例如使用 Vapor 框架)和命令行工具。这门语言旨在防止常见的 bug:变量必须初始化,数组会检查边界,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:)、元组和枚举项。

函数与闭包

参数带有调用者要写出的实参标签,让调用读起来像句子。用 _ 可以省略标签。闭包是写在花括号里的匿名函数。

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 声明的数组完全不能被修改。

标志性特性:可选类型

可能缺失的值具有可选类型,写作 String?。在解包之前,你不能把它当作 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 可以安全地解包,?.(可选链)仅在值存在时才调用方法,?? 提供默认值。强制解包 value! 在值为 nil 时会崩溃,所以请少用。简写形式 if let number 需要 Swift 5.7 或更高版本。

结构体、类、枚举与协议

结构体和枚举是值类型;类是支持继承的引用类型。Swift 代码更倾向于使用结构体和协议(接口),而不是类的层级结构。

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

结构体会自动获得逐成员初始化器(Rect(width:height:))。枚举项可以携带关联值,如 .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 结构体的属性。值需要改变时请使用 var。
  • 以为结构体会像对象那样被共享。修改副本不会改变原来的值;需要共享同一个实例时请使用类。
  • 用整数给 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 中结构体和类有什么区别?

结构体是值类型,赋值或传递时会被复制;类是引用类型,所有变量共享同一个实例。类还支持继承和反初始化器(deinitializer)。

类型后面的问号是什么意思?

Int? 是可选类型:它要么包含一个 Int,要么是 nil。使用其中的值之前,必须先解包,例如用 if let。

全部编码教程