Skip to content

Swift 速查表

Apple 用于 iOS、macOS 及更多平台的语言。

01

基础

变量与常量

对于不会改变的值,默认使用 let;仅在需要修改时才改用 var。Swift 在编译时推断类型,但对于复杂或模糊的类型,显式标注可提高可读性。

swift
var name = "Alice"        // mutable
let age = 30               // immutable constant
let pi: Double = 3.14159   // explicit type
var count: Int = 0
count = 10
print(type(of: age))       // Int

可选类型

可选类型表示值的缺失。使用 if-let 进行安全解包,使用 ?? 提供默认值,仅在你确定值存在时才使用 !(有风险——可能崩溃)。

swift
var nickname: String? = nil
nickname = "Al"
if let n = nickname {
    print("Hi, \(n)")
}
print(nickname ?? "Anonymous")  // nil-coalescing
let force: String = nickname!   // force unwrap (crashes if nil)

元组

元组将多个值组合为一个复合值。适用于从函数返回多个值。命名元素可提高可读性。

swift
let person = (name: "Alice", age: 30)
print(person.name)   // Alice
print(person.0)      // Alice
let (n, a) = person
print("\(n), \(a)")  // Alice, 30

类型转换

Swift 不会在类型之间进行隐式转换。始终使用显式转换(例如 Double(intVal))以避免歧义并防止隐蔽的 bug。

swift
let intVal = 42
let doubleVal = Double(intVal)
let strVal = String(intVal)
let fromStr = Int("100")!   // 100
let invalid = Int("abc")    // nil
print(doubleVal, strVal)

断言与前置条件

使用 assert 进行调试检查(在发布构建中会被移除),使用 precondition 检查所有构建中的关键不变量。两者都有助于尽早发现逻辑错误。

swift
let age = -5
assert(age >= 0, "Age cannot be negative")
precondition(age >= 0, "Age must be non-negative")
// In debug builds, assert crashes if false
// precondition checks in release builds too
02

字符串与字符

字符串插值

使用 \(expr) 的字符串插值可将任何表达式嵌入字符串中。它是类型安全的,并在编译时求值,比格式字符串更安全。

swift
let name = "Alice"
let age = 30
let msg = "Name: \(name), Age: \(age)"
let calc = "5 + 3 = \(5 + 3)"
print(msg)        // Name: Alice, Age: 30
print(calc)       // 5 + 3 = 8

常用字符串方法

Swift 字符串是 Unicode 正确的,这意味着 count 反映的是实际字符(字形簇),而非字节数。使用这些方法而非手动索引以确保安全。

swift
let s = "Hello, World"
print(s.count)              // 12
print(s.uppercased())       // HELLO, WORLD
print(s.lowercased())       // hello, world
print(s.hasPrefix("Hello")) // true
print(s.contains("World"))  // true
print(s.reversed())         // "dlroW ,olleH"

子字符串与索引

字符串索引不是整数,因为字符可能有不同的字节大小。使用 index(_:offsetBy:) 进行导航。子字符串与原始字符串共享内存——转换为 String 以便长期存储。

swift
let s = "Hello, World"
let idx = s.index(s.startIndex, offsetBy: 7)
print(s[idx])                     // W
let sub = s[idx...]               // "World"
let newStr = String(sub)          // convert to String
print(s.prefix(5))                // "Hello"

多行字符串

三引号字符串保留换行和缩进。结尾的 """ 决定基线缩进。适用于 HTML、JSON 或长文本块。

swift
let poem = """
Roses are red,
Violets are blue,
Swift is great,
And so are you.
"""
print(poem)
// Use \\(expr) for interpolation in multiline

字符串构建与拆分

使用 joined(separator:) 以分隔符连接,使用 split(separator:) 进行分词。这比使用 + 连接的手动循环更高效。

swift
let parts = ["apple", "banana", "cherry"]
let joined = parts.joined(separator: ", ")
print(joined)  // apple, banana, cherry
let csv = "a,b,c"
let fields = csv.split(separator: ",")
print(fields)  // ["a", "b", "c"]
let reversed = String(s.reversed())
03

数据结构

数组

数组是有序的、从零索引的集合。使用 append/insert 添加元素,使用 filter/map/reduce 进行转换。优先使用值类型(Array 是结构体)以保证线程安全。

swift
var nums = [1, 2, 3]
nums.append(4)
nums.insert(0, at: 0)
nums[1] = 20
print(nums.count)        // 5
print(nums.contains(20)) // true
let evens = nums.filter { $0 % 2 == 0 }
print(nums.first, nums.last)

字典

字典存储键值对,平均查找时间为 O(1)。使用 default: 来避免访问缺失键时返回 nil。键必须是 Hashable 的。

swift
var ages: [String: Int] = ["Alice": 30]
ages["Bob"] = 25
ages["Alice"] = 31
print(ages["Alice", default: 0])  // 31
for (name, age) in ages {
    print("\(name): \(age)")
}
print(ages.keys.sorted())

集合

集合存储唯一值,成员测试时间为 O(1)。适用于去重和集合操作(并集、交集、差集)。元素必须是 Hashable 的。

swift
var a: Set<Int> = [1, 2, 3]
var b: Set<Int> = [3, 4, 5]
print(a.union(b))           // {1,2,3,4,5}
print(a.intersection(b))    // {3}
print(a.subtracting(b))     // {1,2}
print(a.isSubset(of: b))    // false
a.insert(6)

范围

..< 是半开范围(不含上界),... 是闭范围(包含两端)。范围在循环、切片和使用 ~= 运算符的模式匹配中很有用。

swift
for i in 0..<5 { print(i) }   // 0,1,2,3,4
for i in 0...5 { print(i) }   // 0,1,2,3,4,5
let nums = Array(1...5)       // [1,2,3,4,5]
let prefix = nums.prefix(3)   // [1,2,3]
if 5...10 ~= 7 { print("in range") }

高阶函数

map 转换每个元素,filter 选择匹配的元素,reduce 将所有元素合并为一个。这些是 Swift 函数式编程的基础,可实现简洁、可读的数据管道。

swift
let nums = [1, 2, 3, 4, 5]
let doubled = nums.map { $0 * 2 }       // [2,4,6,8,10]
let evens = nums.filter { $0 % 2 == 0 } // [2,4]
let sum = nums.reduce(0, +)             // 15
let strs = nums.map { String($0) }      // ["1","2",...]
print(doubled, evens, sum)
04

控制流

If / Else If / Else

标准的条件分支。条件必须是布尔值(Bool 类型)。Swift 即使对单条语句也要求使用大括号,以防止类似 Apple 的 goto fail 漏洞之类的 bug。

swift
let score = 85
if score >= 90 {
    print("A")
} else if score >= 80 {
    print("B")
} else if score >= 70 {
    print("C")
} else {
    print("F")
}

Switch(模式匹配)

Swift 的 switch 功能强大:支持值绑定、元组、where 守卫和范围。它必须是穷尽的(default 覆盖其余情况),且默认不会贯穿。

swift
let point = (2, 0)
switch point {
case (0, 0):
    print("origin")
case (_, 0):
    print("on x-axis")
case (0, _):
    print("on y-axis")
case let (x, y) where x == y:
    print("on diagonal")
default:
    print("elsewhere: \(point.0),\(point.1)")
}

For-In 循环

for-in 遍历范围、数组、字典和任何 Sequence。需要索引时使用 enumerated()。使用 _ 忽略不使用的循环变量。

swift
for i in 0..<5 { print(i) }
let fruits = ["apple", "banana"]
for (i, fruit) in fruits.enumerated() {
    print("\(i): \(fruit)")
}
let dict = ["a": 1, "b": 2]
for (k, v) in dict { print("\(k)=\(v)") }
for _ in 0..<3 { print("tick") }

While 与 Repeat-While

while 在每次迭代前检查条件;repeat-while 在之后检查(类似 C 中的 do-while)。当循环体必须至少执行一次时使用 repeat-while。

swift
var n = 5
while n > 0 {
    print(n)
    n -= 1
}
var x = 0
repeat {
    x += 1
} while x < 3
print(x)  // 3

Guard(提前退出)

guard 在先决条件不满足时提供提前退出。解包后的可选值在作用域的其余部分保持可用,从而减少嵌套。优先使用 guard 而非深层的 if-let 嵌套。

swift
func greet(_ name: String?) {
    guard let n = name, !n.isEmpty else {
        print("No name provided")
        return
    }
    print("Hello, \(n)")
    // n is unwrapped and available here
}
greet("Alice")
greet(nil)
05

函数与闭包

函数定义

使用 _ 省略参数标签以提高可读性。默认参数值使参数变为可选。具有单个表达式的函数有隐式返回(Swift 5.9+)。

swift
func greet(_ name: String, greeting: String = "Hello") -> String {
    return "\(greeting), \(name)!"
}
print(greet("Alice"))               // Hello, Alice!
print(greet("Bob", greeting: "Hi")) // Hi, Bob!
func add(_ a: Int, _ b: Int) -> Int { a + b }
print(add(3, 4))  // 7

多返回值

元组允许返回多个命名值。返回可选元组表示可能失败。通过 r.min 或 r.0 访问——命名元素更清晰。

swift
func minMax(_ nums: [Int]) -> (min: Int, max: Int)? {
    guard let first = nums.first else { return nil }
    var mn = first, mx = first
    for n in nums { mn = min(mn, n); mx = max(mx, n) }
    return (mn, mx)
}
if let r = minMax([3, 1, 4, 1, 5]) {
    print("min=\(r.min), max=\(r.max)")
}

闭包

闭包是自包含的功能代码块。使用 $0、$1 作为简写参数名。尾随闭包语法(省略最后一个参数标签)是 map/filter/reduce 的惯用写法。

swift
let square: (Int) -> Int = { x in x * x }
print(square(5))  // 25
let add: (Int, Int) -> Int = { $0 + $1 }
print(add(3, 4))  // 7
let nums = [1, 2, 3]
let doubled = nums.map { $0 * 2 }
print(doubled)  // [2, 4, 6]

逃逸闭包与自动闭包

@escaping 标记在函数返回后存储或调用的闭包(异步回调需要)。@autoclosure 将表达式包装为闭包,延迟其求值(用于 assert)。

swift
var handlers: [() -> Void] = []
func saveHandler(_ fn: @escaping () -> Void) {
    handlers.append(fn)
}
saveHandler { print("done") }
handlers.first?()
// @autoclosure delays evaluation
func logIfTrue(_ cond: @autoclosure () -> Bool) {
    if cond() { print("true") }
}
logIfTrue(2 > 1)

Inout 参数

inout 参数允许函数修改调用者的变量(按引用传递)。谨慎使用——为清晰起见,优先返回新值。& 前缀标记修改位置。

swift
func swap(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}
var x = 10, y = 20
swap(&x, &y)
print(x, y)  // 20 10
06

类与结构体

结构体(值类型)

结构体是值类型——赋值时被复制。用于简单的数据容器。计算属性(distance)在访问时计算。mutating 方法修改 self。

swift
struct Point {
    var x: Double
    var y: Double
    var distance: Double {
        (x * x + y * y).squareRoot()
    }
    mutating func moveBy(dx: Double, dy: Double) {
        x += dx; y += dy
    }
}
var p = Point(x: 3, y: 4)
print(p.distance)  // 5.0

类(引用类型)

类是引用类型——通过引用共享,支持继承和 deinit。当你需要标识、共享可变状态或 Objective-C 互操作性时使用。否则优先使用结构体。

swift
class Person {
    var name: String
    var age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    deinit { print("\(name) deallocated") }
}
let p = Person(name: "Alice", age: 30)
print(p.name)  // Alice

继承与重写

使用 override 重定义父类方法。Swift 使用动态派发,因此会调用子类版本。将方法标记为 final 以防止进一步重写,从而提升性能。

swift
class Animal {
    func speak() { print("...") }
}
class Dog: Animal {
    override func speak() { print("Woof!") }
}
class Cat: Animal {
    override func speak() { print("Meow!") }
}
let pets: [Animal] = [Dog(), Cat()]
for pet in pets { pet.speak() }

属性(计算与懒加载)

计算属性有 get/set 块;set 默认使用 newValue。lazy 将初始化推迟到首次访问时——适用于昂贵或很少需要的属性。必须是 var。

swift
class Circle {
    var radius: Double
    var area: Double {
        get { .pi * radius * radius }
        set { radius = (newValue / .pi).squareRoot() }
    }
    lazy var heavy = loadExpensiveData()
    init(radius: Double) { self.radius = radius }
}
func loadExpensiveData() -> String { "data" }

属性观察者

willSet/didSet 观察属性变化。使用 didSet 验证或响应变化(例如限制值范围、触发 UI 更新)。观察者在 init 期间不会触发。

swift
class Counter {
    var count: Int = 0 {
        willSet { print("about to set \(newValue)") }
        didSet {
            print("was \(oldValue), now \(count)")
            if count > 10 { count = 10 }
        }
    }
}
let c = Counter()
c.count = 5
c.count = 20  // clamped to 10
07

协议与扩展

协议定义

协议定义方法和属性的蓝图。类型通过实现它们来遵循。用于抽象、多态和解耦——类似于 Java/C# 中的接口。

swift
protocol Greetable {
    var name: String { get }
    func greet() -> String
}
struct User: Greetable {
    let name: String
    func greet() -> String { "Hi, \(name)" }
}
let u = User(name: "Alice")
print(u.greet())  // Hi, Alice

协议扩展(默认实现)

协议扩展提供默认实现。这支持追溯建模和代码复用,无需基类。是跨不相关类型共享行为的强大功能。

swift
protocol Describable {
    func describe() -> String
}
extension Describable {
    func describe() -> String { "A \(type(of: self))" }
}
struct Box: Describable { }
let b = Box()
print(b.describe())  // A Box

扩展

扩展为现有类型添加功能(甚至是你不拥有的类型,如 Int)。用于组织代码、添加计算属性或遵循协议。不能添加存储属性。

swift
extension Int {
    var squared: Int { self * self }
    func times(_ block: () -> Void) {
        for _ in 0..<self { block() }
    }
}
print(5.squared)  // 25
3.times { print("hi") }  // prints hi 3 times

泛型

泛型编写灵活、可复用的代码,适用于任何类型同时保持类型安全。使用 <T> 作为类型参数。约束(where T: Equatable)限制允许的类型。

swift
func stackOf<T>(_ items: T...) -> [T] {
    var arr: [T] = []
    for item in items { arr.append(item) }
    return arr
}
struct Stack<Element> {
    private var items: [Element] = []
    mutating func push(_ e: Element) { items.append(e) }
}
let s = stackOf(1, 2, 3)  // [Int]

带关联类型的协议

关联类型让协议使用占位类型,类似于协议的泛型。遵循类型指定实际类型。使用 typealias 使其显式,或让 Swift 推断。

swift
protocol Container {
    associatedtype Item
    var count: Int { get }
    mutating func append(_ item: Item)
    subscript(i: Int) -> Item { get }
}
struct IntStack: Container {
    typealias Item = Int
    private var items: [Int] = []
    var count: Int { items.count }
    mutating func append(_ item: Int) { items.append(item) }
    subscript(i: Int) -> Int { items[i] }
}
08

错误处理

定义与抛出错误

错误遵循 Error 协议(通常是枚举)。throw 表示错误。关联值(coinsNeeded)携带上下文。使用 throws 标记可能失败的函数。

swift
enum VendingError: Error {
    case invalidSelection
    case insufficientFunds(coinsNeeded: Int)
    case outOfStock
}
func vend(item: String, coins: Int) throws -> String {
    guard item == "Candy" else { throw VendingError.invalidSelection }
    guard coins >= 2 else { throw VendingError.insufficientFunds(coinsNeeded: 2) }
    return "Dispensing \(item)"
}

Do-Catch 与 Try

do-catch 处理抛出的错误。try 标记抛出调用。对特定情况进行模式匹配以进行针对性处理。catch-all 处理意外错误。错误沿调用栈向上传播。

swift
do {
    let result = try vend(item: "Candy", coins: 1)
    print(result)
} catch VendingError.insufficientFunds(let needed) {
    print("Need \(needed) more coins")
} catch VendingError.outOfStock {
    print("Sold out")
} catch {
    print("Other error: \(error)")
}

Try? / Try!

try? 将错误转换为 nil(返回 Optional)。try! 假定成功并在出错时崩溃——仅在失败确实不可能时使用。优先使用 try? 配合可选绑定以实现优雅降级。

swift
let result1 = try? vend(item: "Candy", coins: 1)
print(result1)  // nil (error converted to optional)
let result2 = try! vend(item: "Candy", coins: 5)
print(result2)  // Dispensing Candy (crashes if error)
if let r = try? vend(item: "Candy", coins: 5) {
    print(r)
}

Result 类型

Result 将成功或失败编码为值,无需 throw 即可实现异步错误处理。用于回调、异步 API,或当你想存储/链式处理结果时。.get() 在失败时抛出。

swift
enum FetchError: Error { case network, parse }
func fetch(_ url: String) -> Result<String, FetchError> {
    url.isEmpty ? .failure(.network) : .success("data from \(url)")
}
switch fetch("https://api.com") {
case .success(let data): print(data)
case .failure(let err): print("Error: \(err)")
}
let value = try? fetch("").get()

Defer(清理)

defer 安排清理代码在作用域退出时运行,无论方式如何(正常返回、抛出、错误)。用于资源释放(文件、锁)。多个 defer 以 LIFO 顺序运行。

swift
func processFile(_ path: String) throws {
    let handle = openFile(path)
    defer { closeFile(handle) }
    // ... work that might throw ...
    let data = try read(handle)
    // closeFile always runs, even if throw
    print("processed \(data)")
}
09

文件 I/O 与日期/时间

读写文件

Foundation 提供简单的文件 API。write(toFile:atomically:) 安全写入(临时文件 + 重命名)。使用 String(contentsOfFile:) 读取文本。对于大文件,使用 FileHandle 进行流式处理。

swift
import Foundation
let path = "test.txt"
let content = "Hello, File I/O!"
try content.write(toFile: path, atomically: true,
                  encoding: .utf8)
let read = try String(contentsOfFile: path, encoding: .utf8)
print(read)  // Hello, File I/O!
let lines = read.split(separator: "\n")
print(lines.count)

URL 与 FileManager

FileManager 处理文件系统操作(创建、删除、检查存在)。现代 API 使用 URL(而非路径)。.documentDirectory 是 iOS/macOS 上应用的持久化存储。

swift
import Foundation
let fm = FileManager.default
let url = fm.urls(for: .documentDirectory, in: .userDomainMask)[0]
    .appendingPathComponent("data.json")
try "data".write(to: url, atomically: true, encoding: .utf8)
let exists = fm.fileExists(atPath: url.path)
print("Exists: \(exists)")
try fm.removeItem(at: url)  // delete

Date 与 DateFormatter

Date 表示时间点(内部为 UTC)。DateFormatter 在 Date 和 String 之间转换——固定格式解析时始终将 locale 设为 en_US_POSIX 以避免区域设置 bug。

swift
import Foundation
let now = Date()
let fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
fmt.locale = Locale(identifier: "en_US_POSIX")
let str = fmt.string(from: now)
print(str)  // e.g. 2024-01-15 14:30:00
let parsed = fmt.date(from: "2024-01-01 00:00:00")
print(parsed ?? "invalid")

JSON 编码/解码

Codable 自动化 JSON 序列化。JSONEncoder/JSONDecoder 处理转换。让你的类型遵循 Codable——编译器会合成逻辑。使用 CodingKeys 自定义键名。

swift
struct User: Codable {
    let name: String
    let age: Int
}
let user = User(name: "Alice", age: 30)
let json = try JSONEncoder().encode(user)
let str = String(data: json, encoding: .utf8)!
print(str)  // {"name":"Alice","age":30}
let decoded = try JSONDecoder().decode(User.self, from: json)
print(decoded.name)  // Alice

日期计算

Calendar 处理日期算术,考虑时区和夏令时。使用 dateComponents 提取字段或计算差异。切勿使用原始秒数进行日期计算——使用 Calendar API。

swift
import Foundation
let cal = Calendar.current
let now = Date()
let tomorrow = cal.date(byAdding: .day, value: 1, to: now)!
let components = cal.dateComponents([.year, .month, .day], from: now)
print(components.year!, components.month!)
let diff = cal.dateComponents([.hour], from: now, to: tomorrow)
print(diff.hour!)  // 24
10

并发与异步

Async / Await

async/await(Swift 5.5+)使异步代码读起来像同步代码。Task 创建新的异步上下文。async let 并发运行并等待全部完成。消除回调地狱——优先于完成处理程序。

swift
func fetchUser(_ id: Int) async -> String {
    try? await Task.sleep(nanoseconds: 1_000_000_000)
    return "User \(id)"
}
Task {
    let user = await fetchUser(42)
    print(user)  // User 42
    async let u1 = fetchUser(1)
    async let u2 = fetchUser(2)
    let users = await [u1, u2]  // concurrent
    print(users)
}

Task 与取消

Task 表示一个异步工作单元。通过 Task.isCancelled 进行协作式取消——长时间运行的任务应定期检查。task.value 等待结果。取消是协作式的,而非强制的。

swift
let task = Task {
    for i in 1...100 {
        if Task.isCancelled { return }
        try? await Task.sleep(nanoseconds: 100_000_000)
        print(i)
    }
}
Task {
    try? await Task.sleep(nanoseconds: 500_000_000)
    task.cancel()  // request cancellation
}
await task.value  // wait for completion

Actor(线程安全)

Actor(Swift 5.5+)通过序列化访问来保护可变状态免受数据竞争。所有访问都通过 await 进行。用于共享状态时替代锁/队列。编译器验证安全性。

swift
actor Counter {
    private var count = 0
    func increment() { count += 1 }
    func value() -> Int { count }
}
let counter = Counter()
Task {
    await counter.increment()
    print(await counter.value())
}
// Actors serialize access—no data races

GCD(Grand Central Dispatch)

GCD 是传统的并发 API。DispatchQueue.global() 用于后台工作,.main 用于 UI 更新。QoS(.userInitiated、.background)确定任务优先级。对于非异步代码仍然有用。

swift
import Dispatch
DispatchQueue.global(qos: .userInitiated).async {
    let result = heavyComputation()
    DispatchQueue.main.async {
        print("UI update: \(result)")
    }
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    print("delayed by 2s")
}

异步序列

AsyncSequence 支持遍历异步值(类似流)。使用 for await 消费。适用于分页 API、服务器发送事件或任何随时间产生值的源。

swift
let urls = ["url1", "url2", "url3"]
func fetchAll() async {
    for url in urls {
        let data = await fetchData(url)
        print(data)
    }
}
func fetchData(_ url: String) async -> String {
    "data from \(url)"
}
// AsyncSequence: for await item in stream { ... }
11

协议深入

带关联类型的协议

关联类型(associatedtype)让协议声明一个占位类型,由遵循类型指定。这是 Swift 中协议泛型类型参数的等价物。类型从遵循类型的方法中推断。使用 'where' 子句约束关联类型。PAT(带关联类型的协议)不能直接用作存在类型,除非进行类型擦除或使用(Swift 5.7+)'any Container'。

swift
protocol Container {
    associatedtype Item
    var count: Int { get }
    mutating func append(_ item: Item)
    subscript(i: Int) -> Item { get }
}

struct IntStack: Container {
    // associatedtype Item inferred as Int
    private var items: [Int] = []
    var count: Int { items.count }
    mutating func append(_ item: Int) { items.append(item) }
    subscript(i: Int) -> Int { items[i] }
}

// Generic constraint with associated type
func sum<C: Container>(_ c: C) -> Int where C.Item == Int {
    var total = 0
    for i in 0..<c.count { total += c[i] }
    return total
}

协议扩展(默认实现)

协议扩展提供默认实现——遵循类型免费获得方法但可以重写。这是 Swift 追溯为类型添加功能的方式。约束扩展(where Element: Numeric)仅向满足约束的类型添加方法。标准库就是这样为所有 Collection 添加 map/filter/reduce 的。

swift
protocol Describable {
    var description: String { get }
}

// Default implementation via extension
extension Describable {
    var description: String { "A \(type(of: self))" }
}

struct Point: Describable {
    let x, y: Int
    // Uses default description: "A Point"
}

struct Person: Describable {
    let name: String
    var description: String { "Person named (name)" }  // override
}

// Extension with constraints
extension Collection where Element: Numeric {
    var sum: Element { reduce(0, +) }
}
[1, 2, 3].sum  // 6

协议组合与存在类型

协议组合(A & B)要求值遵循多个协议。存在类型(any Protocol)可以持有任何遵循类型,但有运行时派发开销——每次调用都需查找见证表。当你返回一个特定类型但想隐藏它时,使用 'some Protocol'(不透明返回类型)。为性能优先使用泛型而非存在类型;需要异构集合时使用存在类型。

swift
protocol Named { var name: String { get } }
protocol Aged { var age: Int { get } }

struct Person: Named, Aged {
    let name: String
    let age: Int
}

// Protocol composition: requires both protocols
func wishHappyBirthday(to celebrant: Named & Aged) {
    print("Happy birthday, (celebrant.name), you're (celebrant.age)!")
}

let p = Person(name: "Alice", age: 30)
wishHappyBirthday(to: p)

// Existential types (any) — type erasure overhead
let items: [any Named] = [p, Pet(name: "Rex")]
for item in items { print(item.name) }

// Swift 5.7: 'any' keyword required for existentials
// 'some' for opaque types (returns one concrete type)

面向协议编程

面向协议编程是 Swift 从 OOP 的范式转变。围绕带默认实现的协议进行设计,而非类层次结构。优点:适用于值类型(结构体/枚举),支持追溯遵循(扩展你不拥有的类型),实现多重'继承'(一个类型可以遵循多个协议)。类对于引用语义和 Objective-C 互操作仍然有用,但大多数模型优先使用结构体+协议。

swift
// POP: design around protocols, not inheritance
protocol Drawable {
    func draw(on canvas: Canvas)
}

extension Drawable {
    // Default implementation + polymorphism
    func drawTwice(on canvas: Canvas) {
        draw(on: canvas)
        draw(on: canvas)
    }
}

struct Circle: Drawable { let radius: Double
    func draw(on canvas: Canvas) { /* ... */ }
}
struct Square: Drawable { let side: Double
    func draw(on canvas: Canvas) { /* ... */ }
}

// Value types + protocols = no inheritance needed
let shapes: [any Drawable] = [Circle(radius: 5), Square(side: 3)]
shapes.forEach { $0.draw(on: canvas) }

// Retroactive modeling (extend types you don't own)
extension Int: Drawable {
    func draw(on canvas: Canvas) { /* draw the number */ }
}

自定义协议见证

协议作为依赖(Repository)支持在测试和灵活性中交换实现(真实 vs 模拟)。关联类型使协议泛型化。这是 Swift 的依赖注入——注入一个遵循协议的类型。'协议见证'是提供具体行为的遵循类型。这种模式(repository、data source)在 Swift 架构(VIPER、Clean Architecture)中很常见。

swift
// Protocol with requirements
protocol Repository {
    associatedtype Entity
    func get(_ id: Int) -> Entity?
    func save(_ entity: Entity)
}

// Concrete implementation
struct UserRepo: Repository {
    typealias Entity = User
    func get(_ id: Int) -> User? { /* fetch */ nil }
    func save(_ user: User) { /* persist */ }
}

// Generic function using protocol
func display<R: Repository>(_ repo: R, id: Int) where R.Entity: CustomStringConvertible {
    if let entity = repo.get(id) {
        print(entity.description)
    }
}

// Test double for testing
struct MockUserRepo: Repository {
    typealias Entity = User
    var stored: [Int: User] = [:]
    func get(_ id: Int) -> User? { stored[id] }
    func save(_ user: User) { /* no-op for tests */ }
}
12

泛型深入

泛型函数与类型

泛型编写灵活、可复用的代码,适用于任何类型同时保持类型安全。T 是类型参数(占位符)。编译器为性能生成特化版本(无装箱)。泛型类型(Stack<T>)保持其类型参数。Element 是 Array 的泛型类型。泛型在编译时解析——无运行时开销,不同于存在类型。

swift
// Generic function
func swap<T>(_ a: inout T, _ b: inout T) {
    let temp = a; a = b; b = temp
}
var x = 1, y = 2
swap(&x, &y)  // x=2, y=1

// Generic type
struct Stack<T> {
    private var items: [T] = []
    mutating func push(_ item: T) { items.append(item) }
    mutating func pop() -> T? { items.popLast() }
}

var intStack = Stack<Int>()
intStack.push(42)
var strStack = Stack<String>()
strStack.push("hello")

// Generic method on non-generic type
extension Array {
    func chunked(into size: Int) -> [[Element]] {
        stride(from: 0, to: count, by: size).map {
            Array(self[$0..<Swift.min($0 + size, count)])
        }
    }
}

类型约束

类型约束限制可用的类型:T: Equatable 要求 T 遵循协议,T: SomeClass 要求类层次结构。where 子句添加更复杂的约束(例如匹配关联类型)。约束让你使用协议的方法(Equatable 的 ==,Comparable 的 <)。没有约束,你只能赋值和传递 T——无法进行操作。

swift
// Constraint: T must conform to Equatable
func indexOf<T: Equatable>(_ item: T, in array: [T]) -> Int? {
    for (i, elem) in array.enumerated() {
        if elem == item { return i }  // needs Equatable
    }
    return nil
}

// Multiple constraints
func max<T: Comparable>(_ a: T, _ b: T) -> T {
    return a > b ? a : b
}

// Constraint on associated type
protocol Repository {
    associatedtype Entity: Identifiable
    func find(_ id: Entity.ID) -> Entity?
}

// where clause for complex constraints
func merge<C1: Collection, C2: Collection>(
    _ c1: C1, _ c2: C2
) -> [C1.Element] where C1.Element == C2.Element {
    Array(c1) + Array(c2)
}

不透明类型(some)

不透明类型(some Protocol,Swift 5.1)返回一个对调用者隐藏的特定具体类型。与 'any'(存在类型)不同,类型是固定的且编译器已知——无装箱,编译时派发。这是 SwiftUI 'some View' 的基础。当你想隐藏具体类型但保持性能时使用 'some'。调用者可以使用协议方法但不能依赖特定类型。

swift
// Opaque return type: returns ONE concrete type
// (hidden from caller, but fixed per call site)
func makeStack() -> some Collection<Int> {
    return [1, 2, 3]  // concrete type is [Int]
}

let s = makeStack()  // type is 'some Collection<Int>'
print(s.count)  // 3

// Useful for hiding implementation details
protocol Shape { func draw() }
struct Circle: Shape { func draw() { print("circle") } }

func makeShape() -> some Shape {
    return Circle()  // caller doesn't know it's Circle
}

// Difference from existential (any):
// - some: one concrete type, no boxing, compile-time dispatch
// - any: can be different types, runtime dispatch, boxing

// Opaque types in properties
struct View {
    var body: some View { Text("Hello") }  // SwiftUI
}

条件遵循

条件遵循使类型仅在其类型参数满足特定条件时才遵循协议。Array<Int> 是 Equatable 的,因为 Int 是;Array<MyStruct> 不是,除非 MyStruct 是。这会传播:[[Int]] 是 Equatable 的,因为 [Int] 是。标准库广泛使用这一点——Array、Optional、Dictionary 都条件性地遵循 Equatable、Hashable、Codable。

swift
// Array conforms to Equatable only if Element is Equatable
extension Array: Equatable where Element: Equatable {
    static func == (lhs: [Element], rhs: [Element]) -> Bool {
        return lhs.count == rhs.count && lhs.elementsEqual(rhs)
    }
}

[1, 2, 3] == [1, 2, 3]  // true (Int: Equatable)
// [SomeStruct] == [SomeStruct]  // error if SomeStruct isn't Equatable

// Conditional conformance for Hashable
extension Stack: Equatable where T: Equatable {
    static func == (lhs: Stack, rhs: Stack) -> Bool {
        return lhs.items == rhs.items
    }
}

// Enables: [Stack<Int>] == [Stack<Int>] (transitively)

Result 与泛型错误处理

Result<Success, Failure> 是一个泛型枚举,用于可能失败的操作,携带值或错误。当你想推迟错误处理或存储结果时很有用。map 转换成功值;flatMap 链式处理也可能失败的操作。Result { try ... } 转换抛出函数。.get() 转换回抛出。有了 async/await,Result 不那么必要,但对于存储/传递的错误仍然有用。

swift
// Result: built-in generic for success/failure
enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}

func fetchUser(_ id: Int) -> Result<User, FetchError> {
    if id < 0 { return .failure(.invalidId) }
    return .success(User(id: id))
}

switch fetchUser(42) {
case .success(let user): print(user)
case .failure(let err): print(err)
}

// map/flatMap for chaining
let result = fetchUser(1)
    .map { $0.name }
    .flatMap { name in fetchAvatar(name) }

// Throwing function → Result
let result = Result { try throwingFunction() }
// Result → throwing
let value = try result.get()
13

ARC 与内存管理

自动引用计数(ARC)

ARC(自动引用计数)管理类实例(引用类型)的内存。每个强引用增加计数;移除一个则递减。当计数为 0 时,对象立即释放(确定性的,不同于垃圾回收)。结构体/枚举(值类型)不使用 ARC——它们被复制。ARC 无法处理引用循环——你必须使用 weak/unowned 来打破它们。

swift
class Person {
    let name: String
    init(name: String) { self.name = name; print("\(name) initialized") }
    deinit { print("\(name) deinitialized") }
}

var p1: Person? = Person(name: "Alice")  // "Alice initialized"
var p2 = p1  // strong ref count = 2
p1 = nil     // ref count = 1, not deinitialized
p2 = nil     // ref count = 0, "Alice deinitialized"

// ARC tracks strong references:
// - +1 on assignment/copy
// - -1 when variable goes out of scope
// - Object freed when count hits 0
// Unlike GC, deterministic (freed immediately when count=0)

强引用、弱引用和无主引用

强引用(默认)保持对象存活。weak 引用不保持对象存活,并在对象释放时变为 nil(必须是可选 var)。unowned 引用不保持对象存活但是非可选的——仅在你确定被引用对象比引用存活更久时使用(释放后访问会崩溃)。委托模式使用 weak;父子关系(父总是比子存活更久)使用 unowned。

swift
class Person {
    var name: String
    var apartment: Apartment?  // strong
    init(name: String) { self.name = name }
    deinit { print("\(name) deinit") }
}

class Apartment {
    var unit: String
    weak var tenant: Person?  // weak breaks cycle
    init(unit: String) { self.unit = unit }
    deinit { print("Apartment \(unit) deinit") }
}

var alice: Person? = Person(name: "Alice")
var apt: Apartment? = Apartment(unit: "4A")
alice?.apartment = apt
apt?.tenant = alice  // weak, doesn't keep alice alive

alice = nil  // "Alice deinit" (no strong cycle)
apt = nil    // "Apartment 4A deinit"

// unowned: non-optional, assumed to always have a value
// Use when the referenced object outlives the reference
class Customer {
    var card: CreditCard?  // strong
}
class CreditCard {
    unowned let owner: Customer  // non-nil, non-optional
    init(owner: Customer) { self.owner = owner }
}

循环引用与闭包

闭包默认强引用捕获。如果类存储了一个捕获 self 的闭包,就会产生循环引用(两者互相保持存活 → 内存泄漏)。使用捕获列表修复:[weak self](self 变为可选,使用 guard let)或 [unowned self](非可选,nil 时崩溃)。在存储为属性或传递给长生命周期对象(观察者、异步任务)的闭包中始终使用 weak self。

swift
class ViewModel {
    var name = "VM"
    var onUpdate: (() -> Void)?

    func setup() {
        // BAD: closure captures self strongly → cycle
        onUpdate = {
            print(self.name)  // strong capture
        }
        // ViewModel holds closure, closure holds ViewModel → leak!

        // GOOD: capture list with weak self
        onUpdate = { [weak self] in
            guard let self = self else { return }
            print(self.name)
        }

        // GOOD: unowned if self outlives closure
        onUpdate = { [unowned self] in
            print(self.name)
        }
    }
    deinit { print("VM deinit") }
}

逃逸与非逃逸闭包

非逃逸闭包(默认)在函数内运行并被丢弃——无循环引用风险,闭包内无需 self.。@escaping 闭包比函数存活更久(存储在属性中、异步派发等)——它们可能导致循环引用,因此你必须显式使用 self. 并考虑 [weak self]。编译器会强制执行这一点。大多数完成处理程序是 @escaping;map/filter/reduce 是非逃逸的。

swift
class DataLoader {
    var storedHandler: (() -> Void)?

    // @escaping: closure outlives the function (stored or async)
    func load(with handler: @escaping (Data) -> Void) {
        storedHandler = { handler(cachedData) }  // stored
        DispatchQueue.global().async {
            handler(self.fetch())  // escapes via async
        }
    }

    // Non-escaping (default): closure runs during the function
    func process(_ transform: (Int) -> Int) -> [Int] {
        return [1, 2, 3].map(transform)  // runs now, no storage
    }
    // No need for [weak self]—self is alive during the call
}

// @escaping requires explicit self. in closures
class View {
    func load() {
        DataLoader().load { [self] data in  // self. required
            print(data)
        }
    }
}

内存安全与独占性

Swift 强制执行独占性——变量不能从重叠作用域同时被访问(读+写或写+写)。这防止了数据竞争和未定义行为。inout 参数在调用期间获得独占访问。结构体 mutating 方法持有对 self 的独占访问。Actor(Swift 5.5+)在语言层面为共享可变状态强制执行独占性,从设计上消除数据竞争。

swift
// Exclusivity: Swift prevents overlapping access to a variable
// This is a compile-time or runtime error:
var step = 1
func increment(_ n: inout Int) {
    step += 1  // accessing global while n is inout to step
}
// increment(&step)  // ERROR: overlapping access

// Fix with explicit copy
func safeIncrement(_ n: inout Int) {
    var copy = step
    copy += 1
    step = copy
}

// Struct mutating methods have exclusive access
struct Counter {
    var count = 0
    mutating func increment(_ other: inout Counter) {
        count += other.count  // ok, different instances
    }
}

// 'inout' parameters: exclusive access for duration
// 'isolation' (Swift 5.5+): actors enforce exclusivity
14

SwiftUI 基础

视图与修饰符

SwiftUI 是 Apple 的声明式 UI 框架。视图是遵循 View 的结构体,具有 'body' 属性。修饰符(.font、.padding)返回新的包装视图——它们不会修改。视图是轻量级值类型;SwiftUI 对它们进行 diff 以更新实际 UI。声明式风格描述了给定状态下 UI 应有的样子,SwiftUI 处理过渡。

swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(spacing: 20) {
            Text("Hello, World!")
                .font(.title)
                .fontWeight(.bold)
                .foregroundColor(.blue)

            Button(action: { print("tapped") }) {
                HStack {
                    Image(systemName: "star.fill")
                    Text("Tap me")
                }
            }
            .padding()
            .background(Color.orange)
            .cornerRadius(10)
        }
        .padding()
    }
}

// Views are value types (structs), declarative
// Modifiers return new views (immutability)

状态与绑定

@State 用于本地、可变的视图状态——当它改变时,SwiftUI 重新渲染视图。@Binding 让子视图通过 $ 前缀读写父视图的 @State(创建绑定)。状态应是唯一数据源;向下传递绑定,而非要修改的值。对于跨视图的共享/复杂状态,使用 @StateObject/@ObservedObject/@EnvironmentObject 配合 ObservableObject。

swift
struct CounterView: View {
    @State private var count = 0  // local, mutable state

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") { count += 1 }
        }
    }
}

// @Binding: child receives a reference to parent's state
struct StepperView: View {
    @Binding var value: Int
    var body: some View {
        HStack {
            Button("-") { value -= 1 }
            Text("\(value)")
            Button("+") { value += 1 }
        }
    }
}

// Parent passes binding with $
struct ParentView: View {
    @State var score = 0
    var body: some View {
        StepperView(value: $score)  // $ creates a Binding
    }
}

ObservableObject 与 EnvironmentObject

ObservableObject(类)用于跨多个视图的共享状态。@Published 属性在改变时触发视图更新。@StateObject 创建并拥有对象(在层次结构顶部使用)。@EnvironmentObject 将对象注入视图树——任何后代无需通过绑定即可访问。@ObservedObject 接收现有对象(不拥有它)。使用 @StateObject 拥有,@ObservedObject/@EnvironmentObject 接收。

swift
// Model: ObservableObject for shared state
class AppModel: ObservableObject {
    @Published var username: String = ""  // @Published triggers UI update
    @Published var isLoggedIn: Bool = false

    func login() {
        // ... auth logic ...
        isLoggedIn = true
    }
}

// @StateObject: owns the model (created once)
struct RootView: View {
    @StateObject var model = AppModel()
    var body: some View {
        // Inject into environment
        ContentView().environmentObject(model)
    }
}

// @EnvironmentObject: receives from environment
struct ProfileView: View {
    @EnvironmentObject var model: AppModel
    var body: some View {
        Text("Hello, \(model.username)")
    }
}

// @ObservedObject: receives from parent (doesn't own)
struct LoginView: View {
    @ObservedObject var model: AppModel
}

列表与导航

List 渲染可滚动行(类似 UITableView)。项目必须遵循 Identifiable(或你提供 id:)。NavigationStack(iOS 16+)管理导航;NavigationLink 推送目标。LazyVStack/LazyHStack 懒加载内容以提升性能。ForEach 用于在其他容器内重复视图。表格数据使用 List,自定义布局使用 ScrollView+LazyVStack。

swift
struct Item: Identifiable {
    let id = UUID()
    let name: String
}

struct ListView: View {
    let items = [Item(name: "Apple"), Item(name: "Banana")]

    var body: some View {
        NavigationStack {
            List(items) { item in
                NavigationLink(item.name) {
                    DetailView(item: item)
                }
            }
            .navigationTitle("Fruits")
        }
    }
}

struct DetailView: View {
    let item: Item
    var body: some View {
        Text("Detail: \(item.name)")
            .navigationTitle(item.name)
    }
}

// ForEach for custom layouts
ScrollView {
    LazyVStack {
        ForEach(items) { item in
            Text(item.name)
        }
    }
}

表单与 Sheet

Form 自动为设置屏幕样式化控件(分组、平台适配)。常用控件:TextField、Toggle、Slider、Picker、Stepper。.sheet 呈现模态;.fullScreenCover 用于全屏。绑定($)将控件连接到状态。Form 适应平台(iOS 分组列表、macOS 表单布局)。数据输入和设置使用 Form;自定义布局使用 VStack。

swift
struct SettingsView: View {
    @State private var name = ""
    @State private var notifications = true
    @State private var volume: Double = 0.5
    @State private var theme = "Light"
    @State private var showSheet = false

    var body: some View {
        Form {
            Section("Profile") {
                TextField("Name", text: $name)
                Toggle("Notifications", isOn: $notifications)
            }
            Section("Audio") {
                Slider(value: $volume, in: 0...1)
            }
            Section("Appearance") {
                Picker("Theme", selection: $theme) {
                    Text("Light").tag("Light")
                    Text("Dark").tag("Dark")
                }
            }
            Button("Show Modal") { showSheet = true }
        }
        .sheet(isPresented: $showSheet) {
            Text("Modal content")
        }
    }
}
15

闭包深入

闭包语法变体

闭包有多种语法形式。完整形式:{ (params) -> Type in body }。类型推断让你省略类型。简写参数($0、$1)替代命名参数。尾随闭包语法在闭包是最后一个参数时将其移到 () 外。多个尾随闭包(Swift 5.3+)命名附加闭包。使用仍然可读的最简洁形式——简写参数非常适合 map 等短闭包。

swift
// Full closure
let add: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
    return a + b
}

// Type inference
let add2: (Int, Int) -> Int = { a, b in a + b }

// Shorthand argument names ($0, $1...)
let add3: (Int, Int) -> Int = { $0 + $1 }

// Trailing closure syntax
let result = [1, 2, 3].map { $0 * 2 }  // [2, 4, 6]

// Multiple trailing closures (Swift 5.3+)
Button {
    print("action")  // action
} label: {
    Text("Click")    // label
}

// No return for Void closures
let log: (String) -> Void = { msg in print(msg) }

值捕获

闭包从其封闭作用域捕获变量。默认情况下,它们按引用捕获(对捕获变量的更改是可见的)。捕获列表 [foo] 按值捕获(闭包创建时的快照)。对于引用类型(类),[weak self] 或 [unowned self] 打破循环引用。按引用捕获的值类型(结构体)仍能看到更新,因为闭包持有一个盒子。捕获列表位于参数列表之前。

swift
// Closures capture surrounding state
func makeIncrementer(amount: Int) -> () -> Int {
    var total = 0
    return {
        total += amount  // captures total and amount
        return total
    }
}

let inc = makeIncrementer(amount: 5)
inc()  // 5
inc()  // 10
// total persists between calls (captured by reference)

// Capture list controls how
class Foo { var x = 0 }
let foo = Foo()
let closure = { [foo] in  // captures foo by value (snapshot)
    print(foo.x)
}
foo.x = 100
closure()  // prints 0 (captured the old value)

// Without capture list, foo is captured by reference
let refClosure = { print(foo.x) }
foo.x = 200
refClosure()  // prints 200

逃逸与自动闭包

@autoclosure 自动将表达式包装为零参数闭包,因此调用者直接写表达式(无大括号)。这实现了惰性求值——assert() 使用它在发布构建中跳过条件。与 @escaping 结合,你可以推迟求值。&& 和 || 运算符使用 @autoclosure 进行短路求值。谨慎使用 @autoclosure——它隐藏了代码被推迟的事实,可能使读者困惑。

swift
// @autoclosure: wraps an expression in a closure automatically
func log(_ condition: @autoclosure () -> Bool, _ message: String) {
    if condition() {  // evaluated here, not at call
        print(message)
    }
}
log(2 > 1, "two is greater")  // no { } needed

// assert uses @autoclosure to avoid evaluating in release
assert(isValid, "Invalid state")  // isValid not checked in release

// @autoclosure + @escaping
func deferred(_ block: @autoclosure @escaping () -> Int) {
    DispatchQueue.main.async { print(block()) }
}
deferred(expensiveComputation())  // runs later, on main

// Short-circuit evaluation with @autoclosure
func &&(lhs: @autoclosure () -> Bool, rhs: @autoclosure () -> Bool) -> Bool {
    return lhs() ? rhs() : false  // rhs not evaluated if lhs is false
}

高阶函数

Swift 的高阶函数(map、filter、reduce、flatMap、compactMap)在集合上实现函数式编程。map 转换,filter 选择,reduce 聚合,flatMap 扁平化,compactMap 移除 nil。这些返回新集合(不可变性)。链式组合操作。对于大型数据集,使用 .lazy 避免中间数组。这些是函数式 Swift 的基础——转换数据时优先于命令式 for 循环。

swift
let nums = [1, 2, 3, 4, 5, 6]

// map: transform each element
let doubled = nums.map { $0 * 2 }  // [2, 4, 6, 8, 10, 12]

// filter: keep elements matching predicate
let evens = nums.filter { $0 % 2 == 0 }  // [2, 4, 6]

// reduce: combine all into one
let sum = nums.reduce(0, +)  // 21
let product = nums.reduce(1, *)  // 720

// flatMap: transform + flatten
let nested = [[1, 2], [3, 4]]
let flat = nested.flatMap { $0 }  // [1, 2, 3, 4]

// compactMap: transform + filter nils
let strings = ["1", "abc", "3"]
let ints = strings.compactMap { Int($0) }  // [1, 3]

// Chaining (lazy evaluation)
let result = nums
    .filter { $0 % 2 == 0 }
    .map { $0 * $0 }
    .reduce(0, +)  // 4 + 16 + 36 = 56

作为完成处理程序的闭包

完成处理程序是 async/await(Swift 5.5)之前的标准异步模式。它们是 @escaping(稍后运行)并经常使用 Result 进行错误处理。缺点是嵌套回调('末日金字塔')。async/await 使其线性且可读。新代码应使用 async/await;完成处理程序保留用于委托 API 和 Objective-C 互操作。你可以用 withCheckedContinuation 包装完成处理程序以与 async/await 一起使用。

swift
// Completion handler pattern (pre-async/await)
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, _, error in
        if let error = error {
            completion(.failure(error))
            return
        }
        guard let data = data,
              let user = try? JSONDecoder().decode(User.self, from: data) else {
            completion(.failure(DecodeError()))
            return
        }
        completion(.success(user))
    }.resume()
}

// Usage (pyramid of doom)
fetchUser(id: 42) { result in
    switch result {
    case .success(let user):
        fetchAvatar(user) { avatarResult in
            // nested...
        }
    case .failure(let error):
        print(error)
    }
}

// Modern: async/await replaces this
func fetchUserAsync(id: Int) async throws -> User {
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}
16

属性包装器

定义属性包装器

属性包装器(@propertyWrapper)封装可复用的属性行为。wrappedValue 属性是用户交互的对象。init 接收初始值和任何自定义参数。SwiftUI 广泛使用属性包装器:@State、@Binding、@Published、@AppStorage。自定义用于验证、缓存、日志记录或默认值。包装器是管理存储的结构体/类。

swift
@propertyWrapper
struct Clamped<Value: Comparable> {
    var value: Value
    let range: ClosedRange<Value>

    var wrappedValue: Value {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }

    init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
        self.range = range
    }
}

// Usage
struct Slider {
    @Clamped(0...100) var progress: Int = 50
}
var s = Slider()
s.progress = 150  // clamped to 100
s.progress = -10  // clamped to 0
print(s.progress)  // 0

投影值

projectedValue(通过 $ 访问)是属性的辅助接口。SwiftUI 大量使用:@State 的 $ 为子视图提供 Binding,@Published 的 $ 提供 Publisher。当包装器应暴露除包装值之外的内容(绑定、发布者、包装器本身)时定义 projectedValue。$ 前缀语法使其符合人体工程学。并非所有包装器都需要 projectedValue——它是可选的。

swift
@propertyWrapper
struct Observable<T> {
    var value: T
    var wrappedValue: T {
        get { value }
        set { value = newValue; onChange() }
    }
    var onChange: () -> Void = {}

    // projectedValue: accessed via $ (like @State's binding)
    var projectedValue: Observable<T> { self }
}

struct View {
    @Observable var count = 0
    // count: the wrapped value (Int)
    // $count: the projected value (Observable<Int>)
}

// SwiftUI's @State projects a Binding
struct Counter: View {
    @State var value = 0
    var body: some View {
        Stepper("Value: \(value)", value: $value)  // Binding<Int>
    }
}

内置属性包装器

SwiftUI 提供许多属性包装器:@State(本地状态)、@Binding(父状态)、@ObservedObject/@StateObject(外部模型)、@EnvironmentObject(注入模型)、@AppStorage(UserDefaults)、@SceneStorage(场景恢复)、@FocusState(键盘焦点)、@ScaledMetric(动态类型)、@Namespace(动画)。每个都管理存储和生命周期。Combine 的 @Published 触发 UI 更新。了解何时使用哪个是 SwiftUI 的关键。

swift
import SwiftUI
import Combine

struct ContentView: View {
    @State var count = 0  // local view state

    @Binding var externalValue: Int  // parent-owned

    @EnvironmentObject var model: AppModel  // injected

    @AppStorage("username") var username = ""  // UserDefaults

    @SceneStorage("draft") var draft = ""  // per-scene storage

    @FocusState var isFocused: Bool  // focus management

    @ScaledMetric var size = 16  // Dynamic Type scaling

    @Namespace var animationNamespace  // matched geometry

    // Combine
    @Published var data = [Item]()  // in ObservableObject
}

// @AppStorage auto-syncs with UserDefaults
// @SceneStorage restores state per scene (iPad multitasking)

用于验证的自定义包装器

属性包装器在横切关注点上表现出色。NonEmpty 在设置时验证。Cached 惰性计算并记忆化。其他想法:Logged(记录更改)、Trimmed(修剪空白)、Formatted(get 时格式化)、Clamped(范围限制)、UserDefaults 支持的。包装器减少样板代码——你编写一次逻辑并用 @ 应用。它们可组合:@Logged @Clamped(0...100) var value。保持包装器专注于一个关注点。

swift
@propertyWrapper
struct NonEmpty {
    private var value: String = ""
    var wrappedValue: String {
        get { value }
        set {
            guard !newValue.isEmpty else {
                print("Warning: cannot set to empty")
                return
            }
            value = newValue
        }
    }
    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue
    }
}

@propertyWrapper
struct Cached<T> {
    private var cached: T?
    private var generator: () -> T
    var wrappedValue: T {
        mutating get {
            if cached == nil { cached = generator() }
            return cached!
        }
    }
    init(wrappedValue: @autoclosure @escaping () -> T) {
        self.generator = wrappedValue
    }
}

struct Config {
    @NonEmpty var name: String = "default"
    @Cached var expensiveValue: Int = computeExpensive()
}

协议和泛型中的属性包装器

属性包装器可以是泛型的并用于协议中。但是,它们有局限性:具有包装属性的结构体不会获得成员逐一初始化器(你必须手动编写 init),复制可能很棘手(包装器被复制,包括其状态)。对于类,这不太是问题。尽管有局限性,包装器对于声明式、可复用的属性行为很强大。SwiftUI 的设计严重依赖它们。

swift
// Apply to protocol requirements
protocol Configurable {
    @NonEmpty var name: String { get set }
}

// Generic property wrapper
@propertyWrapper
struct Validated<T> {
    private var value: T?
    let validator: (T) -> Bool
    var wrappedValue: T? {
        get { value }
        set {
            if let v = newValue, validator(v) { value = v }
        }
    }
    init(wrappedValue: T?, validator: @escaping (T) -> Bool) {
        self.validator = validator
        self.wrappedValue = wrappedValue
    }
}

struct Form {
    @Validated(validator: { $0.count >= 8 }) var password: String? = nil
}

// Limitation: property wrappers in structs can't be
// initialized from another instance easily (copy issues).
// They work best as stored properties, not computed.
17

Combine 框架

发布者与订阅者

Combine 是 Apple 的响应式框架(类似 RxSwift)。发布者发出值;订阅者接收它们。sink 创建带闭包的订阅者。assign 将输出绑定到属性。运算符(map、filter、reduce)声明式地转换发布者。Combine 是声明式的——描述管道,值流经其中。用于异步操作、UI 绑定和事件处理。现代 Swift 优先使用 async/await,但 Combine 仍用于复杂管道。

swift
import Combine

// Publisher: emits values over time
let publisher = [1, 2, 3].publisher

// Subscriber: receives values
publisher.sink { value in
    print(value)  // 1, 2, 3
}

// With completion
publisher.sink(receiveCompletion: { completion in
    print("done: \(completion)")
}, receiveValue: { value in
    print(value)
})

// Assign to a property
class ViewModel {
    var data: String = ""
}
let vm = ViewModel()
Just("hello").assign(to: &vm.data)

// Operators transform publishers
[1, 2, 3, 4].publisher
    .map { $0 * 2 }
    .filter { $0 > 4 }
    .sink { print($0) }  // 6, 8

@Published 与 ObservableObject

@Published 包装属性并在其改变时向订阅者发出新值。$ 前缀访问发布者。这桥接了 Combine 与 SwiftUI——ObservableObject 中的 @Published 触发视图更新。当 AnyCancellable 被释放时订阅被取消,因此存储它们(例如在 Set 中)。将 Combine 用于防抖、节流、组合多个异步源——这些是 async/await 处理得不够优雅的事情。

swift
class UserStore: ObservableObject {
    @Published var name: String = ""  // publishes on change
    @Published var age: Int = 0

    // $name is a Publisher<String, Never>
    func demo() {
        $name.sink { print("name changed to \($0)") }
        name = "Alice"  // prints "name changed to Alice"
    }
}

// Combine with SwiftUI
struct ProfileView: View {
    @ObservedObject var store: UserStore
    var body: some View {
        Text(store.name)  // updates when name changes
    }
}

// Cancellable: store subscriptions to keep them alive
class Service {
    var cancellables = Set<AnyCancellable>()
    let store = UserStore()

    init {
        store.$name
            .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
            .sink { name in saveToServer(name) }
            .store(in: &cancellables)  // keep alive
    }
}

运算符

Combine 运算符转换、过滤、组合和时间控制发布者。map/scan 转换;filter/选择器选择;merge/zip/combineLatest 组合多个流;debounce/throttle 控制时序。运算符返回新发布者(不可变性、链式)。debounce 等待安静期(即输即搜);throttle 限制速率(按钮点击)。collect 将所有值收集到数组中。这些实现了声明式响应管道。

swift
let publisher = [1, 2, 3, 4, 5].publisher

// Transforming
publisher.map { $0 * 2 }  // [2, 4, 6, 8, 10]
publisher.scan(0, +)  // [1, 3, 6, 10, 15] (running sum)

// Filtering
publisher.filter { $0 % 2 == 0 }  // [2, 4]
publisher.removeDuplicates()  // consecutive dupes
publisher.first()  // just 1

// Combining
let p1 = [1, 2].publisher
let p2 = [3, 4].publisher
p1.merge(with: p2)  // interleaved: 1, 2, 3, 4 (or any order)
p1.zip(p2)  // pairs: (1,3), (2,4)
p1.combineLatest(p2)  // latest of each

// Timing
publisher.delay(for: 1, scheduler: RunLoop.main)
publisher.throttle(for: 1, scheduler: .main, latest: true)
publisher.debounce(for: 0.5, scheduler: .main)
publisher.collect()  // gather all into [1,2,3,4,5]

Subject(手动发布)

Subject 是你可以手动发送值的可变发布者——桥接命令式和响应式代码。PassthroughSubject 广播但不存储(事件流)。CurrentValueSubject 存储最新值(状态)。CurrentValueSubject 的新订阅者立即获得当前值。使用 Subject 将委托、通知或 UI 事件包装到 Combine 管道中。@Published 本质上是一个与 ObservableObject 集成的 CurrentValueSubject。

swift
import Combine

// PassthroughSubject: broadcasts to subscribers, no current value
let subject = PassthroughSubject<Int, Never>()
subject.sink { print($0) }  // subscriber 1
subject.send(1)  // prints 1
subject.send(2)  // prints 2

// CurrentValueSubject: holds current value, new subs get it
let current = CurrentValueSubject<Int, Never>(0)
current.sink { print($0) }  // prints 0 (current)
current.send(1)  // prints 1
current.sink { print($0) }  // prints 1 (current)
current.value  // 1

// Use cases:
// - PassthroughSubject: events (button taps, notifications)
// - CurrentValueSubject: state (like @Published but more control)
// - Bridge imperative code to Combine

// Finish a subject
subject.send(completion: .finished)

错误处理

Combine 发布者有一个 Failure 类型(不会失败时为 Never)。catch 用回退发布者替换错误。retry 在失败时重新订阅(适用于不稳定网络)。mapError 转换错误类型。assertNoFailure 在发生错误时崩溃(在你确定时使用)。Failure == Never 的发布者可在任何地方使用;有错误的发布者需要处理。这使错误处理在响应式管道中显式且可组合。

swift
enum APIError: Error { case network, parse }

// Publishers can fail with an error
let publisher = Fail<Int, APIError>(error: .network)

// catch: replace error with fallback publisher
publisher
    .catch { _ in Just(0) }  // fallback to 0
    .sink { print($0) }  // 0

// retry: retry on failure
URLSession.shared.dataTaskPublisher(for: url)
    .retry(3)  // retry up to 3 times
    .sink(receiveCompletion: { _ in }, receiveValue: { _ in })

// mapError: transform error type
publisher
    .mapError { _ in NSError(domain: "x", code: 1) }

// assertNoFailure: crash if error (when you're sure it won't)
Just(1).assertNoFailure().sink { print($0) }

// Result publisher
let resultPublisher = [1, 2, 3].publisher.setFailureType(to: Error.self)
18

访问控制与代码组织

访问级别

Swift 有五个访问级别。private(最小范围,声明内)、fileprivate(源文件内)、internal(模块内,默认)、public(任何导入者)、open(public + 可子类化/可重写,仅限类)。Swift 5.9 为 SPM 模块添加了 package。从限制开始,按需放宽。库 API 使用 public,应用代码使用 internal,实现细节使用 private。open 用于为子类化设计的框架类。

swift
// public: accessible from any module that imports this
public class PublicAPI {
    public func connect() {}
}

// internal (default): accessible within the module
class InternalClass {
    func helper() {}  // internal by default
}

// fileprivate: accessible within the source file
fileprivate func fileHelper() {}

// private: accessible within the enclosing declaration
class Counter {
    private var count = 0  // only Counter can access
    fileprivate func reset() { count = 0 }  // same file
}

// Swift 5.9: package (for Swift Package Manager)
package struct PackageType {
    package var value: Int
}

// Guidelines:
// - Start restrictive (private), open up as needed
// - public for framework API, internal for implementation
// - private for encapsulation within a type

扩展与组织

扩展让你将类型的功能拆分到多个文件(String+Validation.swift、String+Parsing.swift)并追溯添加遵循。这使文件保持专注且可管理。协议遵循可以在与主类型定义分开的扩展/文件中。条件遵循(where Element: ...)有条件地添加遵循。使用扩展组织:保持核心类型最小化,在专注的扩展文件中添加功能。

swift
// Split a type across files with extensions
// String+Validation.swift
extension String {
    var isValidEmail: Bool {
        contains("@") && contains(".")
    }
}

// String+Parsing.swift
extension String {
    func toURL() -> URL? { URL(string: self) }
}

// Conform to protocols in extensions
extension String: Comparable {  // already conforms, just showing
    // protocol methods
}

// Conditional conformance
extension Array: MyProtocol where Element: MyProtocol {}

// Protocol conformance in a separate file
// (keeps the main type definition clean)
struct User { let id: Int; let name: String }

extension User: Codable {}  // synthesized
extension User: Equatable {}  // synthesized
extension User: CustomStringConvertible {
    var description: String { "User(\(id), \(name))" }
}

模块与包

Swift Package Manager(SPM)通过 Package.swift 定义模块。每个 target 是一个模块——internal 访问在 target 内,public 跨模块。依赖是其他包(git URL)。产品是你的包暴露的内容。SPM 是管理 Swift 代码的现代方式(替代 CocoaPods/Carthage)。Xcode 原生集成 SPM。结构:Sources/MyLibrary/ 用于代码,Tests/MyLibraryTests/ 用于测试。使用模块分离关注点并控制访问。

swift
// Package.swift (Swift Package Manager)
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyLibrary",
    products: [
        .library(name: "MyLibrary", targets: ["MyLibrary"]),
    ],
    dependencies: [
        .package(url: "https://github.com/.../Logging", from: "1.0.0"),
    ],
    targets: [
        .target(name: "MyLibrary", dependencies: ["Logging"]),
        .testTarget(name: "MyLibraryTests", dependencies: ["MyLibrary"]),
    ]
)

// Importing modules
import Foundation
import MyLibrary  // your module
import Logging    // dependency

// Each target is a module
// internal access is within a target/module
// public access crosses module boundaries

初始化器与指定/便利初始化器

类有指定初始化器(完全初始化,必须调用父类的指定初始化器)和便利初始化器(委托给同一类中的另一个初始化器)。这种两级系统确保完整初始化。结构体免费获得成员逐一初始化器。可失败初始化器(init?)在失败时返回 nil。必需初始化器(required init)必须由子类实现。对于大多数代码,优先使用结构体(更简单的初始化)而非类。

swift
class Person {
    let name: String
    let age: Int

    // Designated initializer: fully initializes
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

class Employee: Person {
    let employeeId: String

    // Designated init must call super's designated init
    init(name: String, age: Int, employeeId: String) {
        self.employeeId = employeeId
        super.init(name: name, age: age)  // must call super
    }

    // Convenience init must call another init (same class)
    convenience init(name: String) {
        self.init(name: name, age: 0, employeeId: "TEMP")
    }
}

// Structs: memberwise initializer (free)
struct Point { let x, y: Int }
let p = Point(x: 1, y: 2)  // synthesized

// Failable initializers
struct Config {
    let port: Int
    init?(port: Int) {
        guard port > 0 else { return nil }
        self.port = port
    }
}

可选类型深入

可选类型是 Swift 的空安全机制——一个枚举,要么是 .some(value) 要么是 .none(nil)。你必须解包才能访问值。可选链(?.)安全导航;nil 合并(??)提供默认值;if let/guard let 安全解包。除非你确定,否则避免使用 !(强制解包)——nil 时会崩溃。可选类型强制你显式处理缺失,消除了其他语言中常见的空引用异常。

swift
// Optional: enum with .some(Value) and .none
let maybeInt: Int? = 42
let nilInt: Int? = nil

// Optional chaining (short-circuits on nil)
let name: String? = user?.profile?.name  // nil if any link is nil

// Nil-coalescing (default value)
let value = maybeInt ?? 0  // 42
let value2 = nilInt ?? 0   // 0

// Forced unwrapping (crashes if nil)
let forced = maybeInt!  // 42
// let crash = nilInt!   // RUNTIME CRASH

// Optional binding
if let actual = maybeInt {
    print(actual)  // 42, actual is Int (not optional)
}

// Guard for early exit
func process(_ data: Data?) {
    guard let data = data else { return }
    // data is non-optional here
}

// Optional try
let result = try? parse(json)  // nil on error
let result2 = try! parse(json) // crashes on error

// Multiple binding
if let a = optA, let b = optB, a < b { /* ... */ }
19

内存管理(ARC/weak)

ARC 基础

自动引用计数(ARC)跟踪强引用。当计数为零时,对象被释放。ARC 是确定性的(不同于垃圾回收):当最后一个引用被释放时,deinit 立即运行。大多数时候,ARC 就是有效工作。问题出现在引用循环:两个对象相互强引用,阻止释放。使用 weak 或 unowned 打破循环。

swift
class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}

var reference1: Person?
var reference2: Person?

reference1 = Person(name: "Alice")
// Prints: Alice is being initialized

reference2 = reference1  // Strong reference count: 2

reference1 = nil  // Count: 1
reference2 = nil  // Count: 0, deinit runs
// Prints: Alice is being deinitialized

弱引用

weak 引用不保持被引用对象存活。当对象被释放时,weak 引用自动变为 nil。weak 必须是可选的(var tenant: Person?)。当被引用对象生命周期更短时使用 weak(租户可能离开公寓)。经典用例是委托模式:委托是 weak 以避免循环。weak 有轻微开销(向运行时注册以设置 nil)。

swift
class Person {
    var name: String
    var apartment: Apartment?  // Strong (person owns apartment)
    init(name: String) { self.name = name }
    deinit { print("\(name) deinit") }
}

class Apartment {
    var unit: String
    weak var tenant: Person?  // Weak (breaks cycle)
    init(unit: String) { self.unit = unit }
    deinit { print("Apartment \(unit) deinit") }
}

var alice: Person? = Person(name: "Alice")
var apt: Apartment? = Apartment(unit: "4A")

alice!.apartment = apt
apt!.tenant = alice  // Weak reference, no cycle

alice = nil  // Alice deinit (apartment's tenant becomes nil)
apt = nil    // Apartment deinit

// weak must be optional (becomes nil when deallocated)

无主引用

unowned 引用与 weak 一样,不保持对象存活。与 weak 不同,unowned 是非可选的且不会变为 nil。在释放后访问无主引用会崩溃。当你能保证被引用对象比引用存活更久时使用 unowned(例如,信用卡不能没有其客户而存在)。unowned 的开销比 weak 小。选择:不确定生命周期用 weak,保证生命周期用 unowned。

swift
class Customer {
    let name: String
    var card: CreditCard?  // Strong
    init(name: String) { self.name = name }
    deinit { print("\(name) deinit") }
}

class CreditCard {
    let number: UInt
    unowned let customer: Customer  // Non-optional, no cycle
    init(number: UInt, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card \(number) deinit") }
}

var john: Customer? = Customer(name: "John")
john!.card = CreditCard(number: 1234, customer: john!)

john = nil
// Customer deinit, then CreditCard deinit

// unowned: non-optional, assumed to always have a value
// CRASH if accessed after deallocation
// Use when the referenced object has same or longer lifetime

闭包与捕获列表

闭包默认按引用强捕获,当闭包存储在 self 上时导致循环。捕获列表([weak self] 或 [unowned self])打破循环。weak self 需要可选处理(guard let self = self)。捕获特定值(let name = self.name)以完全避免捕获 self。传递给函数(而非存储)的闭包不会导致循环。对于引用 self 的存储闭包,始终使用捕获列表。

swift
class ViewController {
    var name = "MyVC"
    var onComplete: (() -> Void)?

    // BAD: Strong reference cycle
    func setupBad() {
        onComplete = {
            print("\(self.name) completed")  // Captures self strongly
        }
    }

    // GOOD: Capture list breaks cycle
    func setupGood() {
        onComplete = { [weak self] in
            guard let self = self else { return }
            print("\(self.name) completed")
        }
    }

    // unowned for guaranteed lifetime
    func setupUnowned() {
        onComplete = { [unowned self] in
            print("\(self.name) completed")
        }
    }

    // Capture specific values
    func setupCapture() {
        let name = self.name  // Capture value, not self
        onComplete = {
            print("\(name) completed")
        }
    }

    deinit { print("VC deinit") }
}

检测与修复泄漏

常见泄漏源:强存储的委托(使用 weak)、保留目标的 Timer(在 deinit 中 invalidate)、NotificationCenter 观察者(在 deinit 中移除)。Xcode 内存图调试器可视化对象图并突出显示泄漏。使用 MallocStackLogging 运行以获取分配跟踪。通过将引用设为 nil 并验证 deinit 运行来测试 deinit。Instruments(Leaks 工具)在运行时检测泄漏。始终将设置与拆卸配对。

swift
// Xcode memory graph debugger: visualize reference cycles
// Product > Debug > View Memory Graph Hierarchy

// Common leak patterns:
// 1. Delegate stored as strong
class View {
    weak var delegate: ViewDelegate?  // MUST be weak
}

// 2. Timer retains target
class TimerHolder {
    var timer: Timer?
    func start() {
        // BAD: timer retains self via #selector
        timer = Timer.scheduledTimer(timeInterval: 1,
            target: self, selector: #selector(fire),
            userInfo: nil, repeats: true)
    }
    @objc func fire() {}
    deinit { timer?.invalidate() }  // MUST invalidate
}

// 3. NotificationCenter observer
class Observer {
    init() {
        NotificationCenter.default.addObserver(
            self, selector: #selector(handle),
            name: .someNotification, object: nil)
    }
    @objc func handle() {}
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

// Use Xcode's Debug Memory Graph to find leaks
20

Actor 与 async/await

async/await 基础

async/await(Swift 5.5+)使异步代码看起来同步。async 标记可以挂起的函数;await 标记挂起点。Task 桥接同步到异步上下文。async let 启动并发任务;await 收集结果。使用 async let 进行并行执行,常规 await 进行顺序执行。错误通过 throws/try 传播。编译器在挂起点强制执行 await。async/await 在许多用例中替代完成处理程序和 Combine。

swift
// Async function
func fetchUser(id: Int) async throws -> User {
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// Calling async functions
func loadProfile() async {
    do {
        let user = try await fetchUser(id: 1)
        print("Loaded: \(user.name)")
    } catch {
        print("Error: \(error)")
    }
}

// Task: bridge async to sync context
Task {
    await loadProfile()
}

// Concurrent execution
func loadMultiple() async {
    async let user1 = fetchUser(id: 1)
    async let user2 = fetchUser(id: 2)
    async let user3 = fetchUser(id: 3)

    let users = try await [user1, user2, user3]
    print("Loaded \(users.count) users")
}

// Sequenced vs concurrent
let sequential = try await fetchUser(id: 1)  // Waits
let concurrent = try await fetchUser(id: 2)  // Then waits again

Actor

Actor(Swift 5.5+)是具有序列化访问的引用类型,防止数据竞争。一次只有一个方法在 actor 上执行——无需手动加锁。Actor 方法必须用 await 调用(它们可能挂起)。属性是隔离的,不能从外部直接访问。Actor 是并发代码中共享可变状态的安全默认值。对于大多数并发状态,使用 actor 而非带锁的类。

swift
// Actor: reference type with serialized access
actor BankAccount {
    private var balance: Decimal

    init(balance: Decimal) {
        self.balance = balance
    }

    func deposit(_ amount: Decimal) {
        balance += amount
    }

    func withdraw(_ amount: Decimal) -> Bool {
        guard balance >= amount else { return false }
        balance -= amount
        return true
    }

    func getBalance() -> Decimal { balance }
}

// Usage (must be async)
let account = BankAccount(balance: 100)
Task {
    await account.deposit(50)
    let balance = await account.getBalance()
    print("Balance: \(balance)")  // 150
}

// Actors prevent data races automatically
// Only one method runs at a time per actor instance
// No need for locks or queues

Actor 隔离

Actor 隔离保护状态。nonisolated 成员可以无需 await 访问(对于纯值或不触及隔离状态的方法)。@MainActor 是用于主线程隔离(UI 更新)的全局 actor。@globalActor 创建自定义全局 actor。跨越 actor 边界需要 await。编译器在编译时验证隔离,防止数据竞争。ViewModel 和 UI 代码使用 @MainActor;自定义 actor 用于领域特定隔离。

swift
actor Counter {
    private var count = 0

    // Isolated method (default)
    func increment() { count += 1 }

    // Nonisolated: can be called without await
    nonisolated var description: String { "Counter" }

    // nonisolated function
    nonisolated func id() -> String { "counter-\(ObjectIdentifier(self))" }
}

// Global actor: @MainActor runs on main thread
@MainActor
class ViewModel: ObservableObject {
    @Published var data: [Item] = []

    func loadData() async {
        // Always runs on main thread
        data = try await fetchItems()
    }
}

// Mark specific functions
class Service {
    @MainActor
    func updateUI(_ result: Result) {
        // UI updates on main thread
    }
}

// globalActor lets you apply to types, functions, properties
@globalActor
actor MyActor {
    static let shared = MyActor()
}

结构化并发

任务组支持动态并行计算。addTask 派生子任务;for await group 在结果完成时收集。任务组强制结构化并发:所有子任务在父任务继续之前完成。取消从父任务自动传播到子任务。在长时间运行的任务中检查 Task.isCancelled。withThrowingTaskGroup 传播错误,在抛出时取消兄弟任务。使用任务组实现扇出/扇入模式。

swift
// TaskGroup: dynamic parallel computation
func fetchAllUsers(ids: [Int]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask { try await fetchUser(id: id) }
        }

        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

// Task tree: parent-child relationship
func process() async {
    await withTaskGroup(of: Void.self) { group in
        for i in 1...5 {
            group.addTask {
                await processItem(i)
            }
        }
        // All tasks complete before group returns
    }
    print("All done")
}

// Cancellation
func longRunning() async {
    await withTaskGroup(of: Void.self) { group in
        group.addTask {
            for i in 1...100 {
                if Task.isCancelled { return }
                await doWork(i)
            }
        }
    }
}

// Cancel from outside
let task = Task { await longRunning() }
task.cancel()

AsyncSequence 与流

AsyncSequence 是 Sequence 的异步版本,支持 for await 循环。AsyncStream 将基于回调或基于委托的 API 桥接到 async/await。yield 发出值,finish 终止。onTermination 清理资源。AsyncThrowingStream 支持错误。将 AsyncSequence 用于分页 API、实时数据或任何随时间产生值的流。标准库在 URL 上提供 .lines 用于逐行读取文件。AsyncSequence 与任务取消集成。

swift
// AsyncSequence: iterate asynchronously
struct Counter: AsyncSequence {
    struct AsyncIterator: AsyncIteratorProtocol {
        var current: Int
        let max: Int
        mutating func next() async -> Int? {
            guard current <= max else { return nil }
            defer { current += 1 }
            try? await Task.sleep(nanoseconds: 100_000_000)
            return current
        }
    }

    let start: Int
    let max: Int

    func makeAsyncIterator() -> AsyncIterator {
        AsyncIterator(current: start, max: max)
    }
}

// Usage
for await num in Counter(start: 1, max: 5) {
    print(num)  // 1 2 3 4 5 (with delays)
}

// AsyncStream: bridge callbacks to async
func urlLines(_ url: URL) -> AsyncThrowingStream<String, Error> {
    AsyncThrowingStream { continuation in
        let task = Task {
            do {
                for try await line in url.lines {
                    continuation.yield(line)
                }
                continuation.finish()
            } catch {
                continuation.finish(throwing: error)
            }
        }
        continuation.onTermination = { _ in task.cancel() }
    }
}
21

测试

XCTest 基础

XCTest 是标准测试框架。XCTestCase 分组相关测试。setUp/tearDown 在每个测试前后运行。断言:XCTAssertEqual、XCTAssertNil、XCTAssertThrowsError、XCTAssertTrue。@testable import 访问内部符号。测试默认在主线程运行。对可选项使用 XCTAssertNotNil。用 Cmd+U 运行测试。Xcode 内联显示测试结果。在代码旁边编写测试以获得快速反馈。

swift
import XCTest
@testable import MyApp

class UserTests: XCTestCase {
    var user: User!

    override func setUp() {
        super.setUp()
        user = User(name: "Alice", age: 30)
    }

    override func tearDown() {
        user = nil
        super.tearDown()
    }

    func testName() {
        XCTAssertEqual(user.name, "Alice")
    }

    func testAgeAfterBirthday() {
        user.haveBirthday()
        XCTAssertEqual(user.age, 31)
    }

    func testValidation() {
        XCTAssertThrowsError(try User(name: "", age: -1)) { error in
            guard let error = error as? UserError else {
                XCTFail("Wrong error type")
                return
            }
            XCTAssertEqual(error, .invalidName)
        }
    }

    func testOptional() {
        XCTAssertNotNil(user.email)
        XCTAssertNil(user.address)
    }
}

// Run: Cmd+U in Xcode

异步测试

Swift 5.5+ 直接支持异步测试方法(async throws func test...)。在测试内使用 try await。对于旧代码,使用 XCTestExpectation:创建期望,在回调中 fulfill(),wait(for:timeout:)。设置合理的超时以捕获挂起。对于测试异步序列,使用 for await 循环。用 URLProtocol 或依赖注入模拟网络调用。测试成功和失败路径。异步测试默认并发运行——如果顺序重要,使用 .serialized 特性。

swift
class APITests: XCTestCase {
    func testFetchUser() async throws {
        let user = try await fetchUser(id: 1)
        XCTAssertEqual(user.name, "Alice")
    }

    func testFetchFailure() async {
        do {
            _ = try await fetchUser(id: -1)
            XCTFail("Should have thrown")
        } catch {
            // Expected
        }
    }

    // Old style with expectations (still works)
    func testAsyncWithExpectation() {
        let expectation = XCTestExpectation(description: "Fetch completes")

        fetchUserAsync { result in
            switch result {
            case .success(let user):
                XCTAssertEqual(user.name, "Alice")
            case .failure:
                XCTFail("Should succeed")
            }
            expectation.fulfill()
        }

        wait(for: [expectation], timeout: 5)
    }
}

模拟与存根

依赖注入实现测试:为依赖定义协议,在测试中注入模拟。MockNetworkService 返回预定结果。通过设置 mock.fetchUserResult 测试成功和失败情况。这种模式将测试与网络解耦,使其快速且确定。对于复杂模拟,使用 Cuckoo 或 Mockingbird 等库。测试类型的公共 API,而非实现细节。模拟应简单且专注。

swift
// Protocol for dependency injection
protocol NetworkService {
    func fetchUser(id: Int) async throws -> User
}

// Production implementation
class APIService: NetworkService {
    func fetchUser(id: Int) async throws -> User {
        // Real network call
    }
}

// Mock for testing
class MockNetworkService: NetworkService {
    var fetchUserResult: Result<User, Error>?

    func fetchUser(id: Int) async throws -> User {
        switch fetchUserResult {
        case .success(let user): return user
        case .failure(let error): throw error
        case .none: throw URLError(.badURL)
        }
    }
}

class ViewModelTests: XCTestCase {
    func testLoadUser() async throws {
        let mock = MockNetworkService()
        mock.fetchUserResult = .success(User(name: "Alice", age: 30))

        let vm = ViewModel(service: mock)
        await vm.load()

        XCTAssertEqual(vm.userName, "Alice")
    }
}

UI 测试

UI 测试通过辅助功能标识符自动化用户交互。XCUIApplication 启动应用。通过辅助功能标识符查找元素(在代码中用 .accessibilityIdentifier 设置)。操作:tap、typeText、swipeLeft/Right。断言:waitForExistence、exists、hittable。UI 测试比单元测试慢但能捕获集成 bug。在 SwiftUI 中用 .accessibilityIdentifier("Email") 设置辅助功能标识符。在多个设备/模拟器上运行 UI 测试以获得覆盖率。

swift
import XCTest

class AppUITests: XCTestCase {
    override func setUpWithError() throws {
        continueAfterFailure = false
        let app = XCUIApplication()
        app.launch()
    }

    func testLoginFlow() {
        let app = XCUIApplication()

        let emailField = app.textFields["Email"]
        let passwordField = app.secureTextFields["Password"]
        let loginButton = app.buttons["Log In"]

        emailField.tap()
        emailField.typeText("[email protected]")

        passwordField.tap()
        passwordField.typeText("password123")

        loginButton.tap()

        // Verify navigation
        XCTAssertTrue(app.staticTexts["Welcome, Alice!"].waitForExistence(timeout: 5))
    }

    func testSwipeAndTap() {
        let cell = app.cells["Item 5"]
        cell.swipeLeft()
        app.buttons["Delete"].tap()
        XCTAssertFalse(cell.exists)
    }
}

性能与代码覆盖率

measure 基准测试代码性能,与基线比较。在 Xcode 中设置基线;如果回归超过阈值则测试失败。measureMetrics 跟踪特定指标(峰值内存、CPU)。在 scheme 中启用代码覆盖率以查看未测试的代码路径。业务逻辑目标 80%+ 覆盖率。快照测试(swift-snapshot-testing)捕获 UI 用于视觉回归。用 Instruments(Time Profiler、Allocations)进行更深入分析。性能测试尽早捕获回归。

swift
class PerformanceTests: XCTestCase {
    func testSortingPerformance() {
        let data = (1...10000).shuffled()

        measure {
            _ = data.sorted()
        }
    }

    func testMemoryUsage() {
        measureMetrics([XCTPerformanceMetric.peakMemory]) {
            _ = (0..<10000).map { _ in [Int](repeating: 0, count: 100) }
        }
    }

    // Baseline comparison
    func testWithBaselines() {
        let options = XCTMeasureOptions()
        options.defaultOptions.measurementUnits = .seconds

        measure(options) {
            // Code to benchmark
        }
    }
}

// Code coverage: enable in Edit Scheme > Test > Options
// View coverage: Cmd+Shift+Y (Report Navigator) > Coverage

// Snapshot testing (third-party)
// func testAppearance() {
//     assertSnapshot(of: view, as: .image)
// }
22

Swift 并发

async/await

async/await(Swift 5.5+)简化异步代码。async 标记可以挂起的函数。await 标记挂起点。Task 创建异步上下文。比完成处理程序清晰得多。错误通过 try 传播。

swift
func fetchData() async throws -> String {
    let (data, _) = try await URLSession.shared.data(from: url)
    return String(data: data, encoding: .utf8) ?? ""
}
// Call from async context
Task {
    let result = try await fetchData()
    print(result)
}

Actor

actor(Swift 5.5+)是具有自动互斥的引用类型。一次只有一个任务访问其状态。从外部调用时方法隐式异步。替代共享可变状态的锁和派发队列。设计上线程安全。

swift
actor Counter {
    private var count = 0
    func increment() {
        count += 1
    }
    func getValue() -> Int {
        count
    }
}
// Usage
Task {
    let counter = Counter()
    await counter.increment()
    let value = await counter.getValue()
}

异步序列

AsyncSequence 是 Sequence 的异步等价物。for await 异步迭代。适用于流式数据(网络、文件行)。实现 AsyncIteratorProtocol 创建自定义异步序列。编译器处理挂起和取消。

swift
for try await item in asyncSequence {
    print(item)
}
// Custom AsyncSequence
struct Counter: AsyncSequence {
    struct AsyncIterator: AsyncIteratorProtocol {
        var current = 0
        mutating func next() async -> Int? {
            current += 1
            return current <= 5 ? current : nil
        }
    }
    func makeAsyncIterator() -> AsyncIterator { AsyncIterator() }
}

任务组

任务组并发运行多个任务并收集结果。addTask 添加子任务。for await 在结果完成时迭代。所有任务必须在组返回之前完成。结构化并发:取消传播到子任务。结果被安全收集。

swift
let results = await withTaskGroup(of: Int.self) { group in
    for i in 1...5 {
        group.addTask { i * i }
    }
    var sum = 0
    for await result in group {
        sum += result
    }
    return sum
}

续体

withCheckedContinuation 将完成处理程序 API 桥接到 async/await。resume(returning:) 恢复异步函数。必须恰好调用一次。withCheckedThrowingContinuation 支持错误。适用于用现有 API 采用 async/await。

swift
func fetchWithCompletion(_ completion: @escaping (String) -> Void) {
    // Legacy completion handler API
    completion("data")
}
// Wrap in async
func fetchAsync() async -> String {
    await withCheckedContinuation { continuation in
        fetchWithCompletion { result in
            continuation.resume(returning: result)
        }
    }
}
23

SwiftUI 深入

视图修饰符

修饰符包装视图以改变外观或行为。顺序很重要:后面的修饰符包装前面的。.padding 在 .background 之前将内边距放在背景内。修饰符返回新的 View 实例。链式组合实现复杂样式。

swift
Text("Hello")
    .font(.title)
    .foregroundColor(.blue)
    .padding()
    .background(Color.gray)
    .cornerRadius(8)
    .shadow(radius: 4)

列表

List 显示可滚动行。ForEach 从数据生成行。onDelete 启用滑动删除。其他修饰符:onMove、onInsert。ForEach 需要可识别数据(id 属性)。列表样式:.plain、.insetGrouped、.sidebar。

swift
List {
    ForEach(items) { item in
        HStack {
            Text(item.name)
            Spacer()
            Text("\(item.price)")
        }
    }
    .onDelete { indexSet in
        items.remove(atOffsets: indexSet)
    }
}
.listStyle(.insetGrouped)

导航

NavigationStack 管理视图栈。NavigationLink 推送目标。navigationTitle 设置标题。toolbar 添加导航栏按钮。适用于 iOS 16+。旧版本使用 NavigationView。Sheet 和 alert 使用 .sheet 和 .alert 修饰符。

swift
NavigationStack {
    List(items) { item in
        NavigationLink(item.name) {
            DetailView(item: item)
        }
    }
    .navigationTitle("Items")
    .toolbar {
        ToolbarItem(placement: .navigationBarTrailing) {
            Button("Add") { addItem() }
        }
    }
}

状态管理

@State 用于本地视图状态(值类型)。@Binding 用于传入的状态。@StateObject 用于拥有的可观察对象(创建一次)。@ObservedObject 用于外部可观察对象。@EnvironmentObject 用于应用范围状态。@State 改变触发视图更新。

swift
struct CounterView: View {
    @State private var count = 0
    @Binding var selected: Int
    @StateObject var viewModel = ViewModel()
    var body: some View {
        Button("\(count)") { count += 1 }
    }
}

动画

withAnimation 动画化状态变化。.spring、.easeInOut 是动画曲线。.animation(value:) 在值改变时动画化。.scaleEffect、.opacity、.offset 可动画化。隐式动画使用 .animation 修饰符。显式使用 withAnimation。

swift
struct AnimatedView: View {
    @State private var scale: CGFloat = 1.0
    var body: some View {
        Button("Tap") {
            withAnimation(.spring(response: 0.3)) {
                scale = scale == 1 ? 1.5 : 1
            }
        }
        .scaleEffect(scale)
        .animation(.easeInOut(duration: 0.3), value: scale)
    }
}
24

Combine 框架

发布者与订阅者

Combine 是 Apple 的响应式框架。发布者随时间发出值。订阅者接收它们。sink 创建订阅者。Just 发出单个值。PassthroughSubject 是手动发布者。Cancellable 必须被保留,否则订阅取消。类似 RxSwift。

swift
import Combine

let publisher = Just(42)
let cancellable = publisher.sink { value in
    print("Received: \(value)")
}
// Just emits one value then finishes
// PassthroughSubject: manual values
let subject = PassthroughSubject<Int, Never>()
subject.send(1)
subject.send(2)

运算符

运算符转换发布者输出。map 转换值。filter 选择值。debounce 延迟到安静。throttle 限制速率。combineLatest 合并流。flatMap 链式发布者。运算符是惰性的:在 sink 订阅之前什么都不会发生。

swift
let publisher = (1...10).publisher
    .map { $0 * 2 }
    .filter { $0 > 10 }
    .sink { print($0) }  // 12, 14, 16, 18, 20

// Debounce
let debounced = subject
    .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
    .sink { print($0) }

@Published

@Published 将属性暴露为 Combine 发布者。$ 前缀访问发布者。改变发出新值。与 ObservableObject 一起用于 SwiftUI。发布者在订阅时发出当前值。适用于无需手动通知的响应式 UI 更新。

swift
class ViewModel: ObservableObject {
    @Published var count = 0
}
let vm = ViewModel()
let cancellable = vm.$count.sink { newCount in
    print("Count changed: \(newCount)")
}
vm.count = 1  // Prints "Count changed: 1"
vm.count = 2  // Prints "Count changed: 2"

Future

Future 将完成处理程序 API 包装为发布者。promise(.success) 发出值。promise(.failure) 发出错误。Future 恰好发出一次。适用于将回调 API 桥接到 Combine。闭包急切运行,除非用 .delay 或 deferred 包装。

swift
func fetchUser() -> Future<User, Error> {
    Future { promise in
        URLSession.shared.dataTask(with: url) { data, _, error in
            if let error = error { promise(.failure(error)) }
            else { promise(.success(parse(data!))) }
        }.resume()
    }
}
// Usage
fetchUser().sink(receiveCompletion: { _ in }, receiveValue: { user in print(user) })

错误处理

Fail 立即发出错误。catch 用另一个发布者替换失败的发布者。retry 在失败时重新订阅。assertNoFailure 在出错时崩溃(用于调试)。完成事件表示没有更多值。错误向下游传播,除非被捕获。

swift
let publisher = Fail<Int, MyError>(error: .notFound)
    .catch { error in
        Just(0)  // Fallback value
    }
    .sink { print($0) }  // 0

// retry on failure
let retried = requestPublisher
    .retry(3)
    .sink(receiveCompletion: { _ in }, receiveValue: { _ in })
25

内存管理

ARC 基础

ARC(自动引用计数)跟踪引用。当计数为零时,deinit 运行并释放内存。强引用增加计数。引用循环阻止释放。ARC 是确定性的(不同于 GC)。当最后一个引用释放时 deinit 同步运行。

swift
class Person {
    let name: String
    init(name: String) { self.name = name }
    deinit { print("\(name) is being deinitialized") }
}
var p1: Person? = Person(name: "Alice")
p1 = nil  // Prints "Alice is being deinitialized"

弱引用

weak 引用不增加保留计数。对象释放时自动设为 nil。必须是可选 var。用于委托和观察者以打破循环。weak 引用是归零的:对象消失后访问是安全的。循环引用最常见的修复方法。

swift
class View {
    weak var delegate: Delegate?
}
class Delegate {
    var view: View?  // strong
}
// weak: does not keep object alive, auto-set to nil
// Use for delegate patterns to avoid cycles

无主引用

unowned 引用不增加保留计数。与 weak 不同,它们是非可选的且不归零。释放后访问会崩溃。当被引用对象比引用者存活更久或同时消亡时使用。比 weak 快(无 nil 检查)。在 self 比闭包存活更久的闭包捕获 self 时常见。

swift
class Customer {
    var card: CreditCard?
}
class CreditCard {
    unowned let customer: Customer
    init(customer: Customer) { self.customer = customer }
}
// unowned: does not keep alive, but not optional
// CRASHES if accessed after dealloc
// Use when the other object has same or shorter lifetime

循环引用

当对象相互强引用时发生循环引用。两者都不释放,泄漏内存。用 weak 或 unowned 修复。在委托、观察者和闭包中常见。闭包默认强捕获 self。[weak self] 或 [unowned self] 打破循环。使用内存图调试器查找循环。

swift
// BAD: retain cycle
class Node {
    var next: Node?
    var prev: Node?  // strong -> cycle
}
// GOOD: break with weak
class Node {
    var next: Node?
    weak var prev: Node?
}
// Closures: use [weak self]
{ [weak self] in self?.update() }

闭包与捕获

闭包按引用捕获变量。存储的闭包(逃逸)如果捕获 self 会创建循环引用。[weak self] 使捕获可选且弱。@escaping 标记比函数调用存活更久的闭包。非逃逸闭包(默认)不会导致循环。编译器警告潜在循环。

swift
class Timer {
    var handler: (() -> Void)?
    func start() {
        handler = { [weak self] in
            self?.tick()  // weak to avoid cycle
        }
    }
    func tick() { /* ... */ }
}
// Escaping closure: stored for later
// @escaping marks escaping closures
26

错误处理深入

自定义错误

自定义错误遵循 Error。LocalizedError 提供 errorDescription。关联值携带上下文。枚举是定义错误的惯用方式。每个 case 代表一种不同的失败。Switch 穷尽性确保所有错误都被处理。遵循 CustomStringConvertible 以获得调试输出。

swift
enum APIError: Error, LocalizedError {
    case invalidURL
    case unauthorized
    case serverError(Int)
    case decodingFailed(Error)

    var errorDescription: String? {
        switch self {
        case .invalidURL: return "Invalid URL"
        case .unauthorized: return "Unauthorized"
        case .serverError(let code): return "Server error: \(code)"
        case .decodingFailed(let err): return "Decode failed: \(err)"
        }
    }
}

Result 类型

Result 是类型化的成功/失败枚举。适用于可能失败的同步操作。map 转换成功值。flatMap 链式操作。get() 抛出以转换为 try/catch。对于存储或传递的结果,Result 优先于抛出函数。将错误处理与值语义结合。

swift
func fetchUser(id: Int) -> Result<User, APIError> {
    guard id > 0 else { return .failure(.invalidURL) }
    return .success(User(name: "Alice"))
}
// Usage
switch fetchUser(id: 1) {
case .success(let user): print(user)
case .failure(let error): print(error)
}
// map and flatMap
let result = fetchUser(id: 1).map { $0.name }

Rethrows

rethrows 从闭包参数传播错误。如果闭包不抛出,函数也不抛出。这避免了对传递非抛出闭包的调用者强制 try。被 map、filter 和其他高阶函数使用。函数本身不能独立于闭包抛出。

swift
func withLock<T>(_ lock: NSLock, _ body: () throws -> T) rethrows -> T {
    lock.lock()
    defer { lock.unlock() }
    return try body()
}
// Only throws if body throws
let x = withLock(lock) { 5 }  // no try needed
let y = try withLock(lock) { try throwingFunc() }

错误传播

try 将错误传播给调用者。try? 将错误转换为 nil(返回 Optional)。try! 在出错时崩溃(在确定时使用)。错误沿调用栈向上冒泡,直到被捕获。抛出的函数必须标记 throws。编译器强制执行 try 标记,防止未处理的错误。defer 无论是否抛出错误都会运行。

swift
func process() throws {
    let data = try fetchData()  // may throw
    let user = try parse(data)  // may throw
    save(user)                  // does not throw
}
// try?: converts to optional, nil on error
let user = try? fetchUser()
// try!: asserts no error, crashes if thrown
let user = try! fetchUser()

用 defer 清理

defer 安排在作用域退出时运行清理。无论作用域如何退出(return、throw 或贯穿)都运行。多个 defer 以 LIFO 顺序运行。适用于关闭文件、释放锁、释放资源。defer 不能抛出或 break/continue。闭包按引用捕获变量。

swift
func processFile() throws {
    let file = open("file.txt")
    defer { close(file) }  // runs on scope exit

    let data = try read(file)
    // ... process ...
    // close() runs here even if read throws
}
// Multiple defers run in reverse order (LIFO)
defer { print("1") }
defer { print("2") }  // prints 2 then 1

这篇内容对您有帮助吗?

学习路径

从零开始学习

通过结构化课程从头学习这个语言。