基础
变量与常量
对于不会改变的值,默认使用 let;仅在需要修改时才改用 var。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 进行安全解包,使用 ?? 提供默认值,仅在你确定值存在时才使用 !(有风险——可能崩溃)。
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)元组
元组将多个值组合为一个复合值。适用于从函数返回多个值。命名元素可提高可读性。
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。
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 检查所有构建中的关键不变量。两者都有助于尽早发现逻辑错误。
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字符串与字符
字符串插值
使用 \(expr) 的字符串插值可将任何表达式嵌入字符串中。它是类型安全的,并在编译时求值,比格式字符串更安全。
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 反映的是实际字符(字形簇),而非字节数。使用这些方法而非手动索引以确保安全。
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 以便长期存储。
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 或长文本块。
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:) 进行分词。这比使用 + 连接的手动循环更高效。
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())数据结构
数组
数组是有序的、从零索引的集合。使用 append/insert 添加元素,使用 filter/map/reduce 进行转换。优先使用值类型(Array 是结构体)以保证线程安全。
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 的。
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 的。
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)范围
..< 是半开范围(不含上界),... 是闭范围(包含两端)。范围在循环、切片和使用 ~= 运算符的模式匹配中很有用。
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 函数式编程的基础,可实现简洁、可读的数据管道。
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)控制流
If / Else If / Else
标准的条件分支。条件必须是布尔值(Bool 类型)。Swift 即使对单条语句也要求使用大括号,以防止类似 Apple 的 goto fail 漏洞之类的 bug。
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 覆盖其余情况),且默认不会贯穿。
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()。使用 _ 忽略不使用的循环变量。
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。
var n = 5
while n > 0 {
print(n)
n -= 1
}
var x = 0
repeat {
x += 1
} while x < 3
print(x) // 3Guard(提前退出)
guard 在先决条件不满足时提供提前退出。解包后的可选值在作用域的其余部分保持可用,从而减少嵌套。优先使用 guard 而非深层的 if-let 嵌套。
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)函数与闭包
函数定义
使用 _ 省略参数标签以提高可读性。默认参数值使参数变为可选。具有单个表达式的函数有隐式返回(Swift 5.9+)。
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 访问——命名元素更清晰。
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 的惯用写法。
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)。
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 参数允许函数修改调用者的变量(按引用传递)。谨慎使用——为清晰起见,优先返回新值。& 前缀标记修改位置。
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类与结构体
结构体(值类型)
结构体是值类型——赋值时被复制。用于简单的数据容器。计算属性(distance)在访问时计算。mutating 方法修改 self。
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 互操作性时使用。否则优先使用结构体。
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 以防止进一步重写,从而提升性能。
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。
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 期间不会触发。
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协议与扩展
协议定义
协议定义方法和属性的蓝图。类型通过实现它们来遵循。用于抽象、多态和解耦——类似于 Java/C# 中的接口。
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协议扩展(默认实现)
协议扩展提供默认实现。这支持追溯建模和代码复用,无需基类。是跨不相关类型共享行为的强大功能。
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)。用于组织代码、添加计算属性或遵循协议。不能添加存储属性。
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)限制允许的类型。
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 推断。
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] }
}错误处理
定义与抛出错误
错误遵循 Error 协议(通常是枚举)。throw 表示错误。关联值(coinsNeeded)携带上下文。使用 throws 标记可能失败的函数。
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 处理意外错误。错误沿调用栈向上传播。
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? 配合可选绑定以实现优雅降级。
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() 在失败时抛出。
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 顺序运行。
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)")
}文件 I/O 与日期/时间
读写文件
Foundation 提供简单的文件 API。write(toFile:atomically:) 安全写入(临时文件 + 重命名)。使用 String(contentsOfFile:) 读取文本。对于大文件,使用 FileHandle 进行流式处理。
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 上应用的持久化存储。
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) // deleteDate 与 DateFormatter
Date 表示时间点(内部为 UTC)。DateFormatter 在 Date 和 String 之间转换——固定格式解析时始终将 locale 设为 en_US_POSIX 以避免区域设置 bug。
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 自定义键名。
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。
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并发与异步
Async / Await
async/await(Swift 5.5+)使异步代码读起来像同步代码。Task 创建新的异步上下文。async let 并发运行并等待全部完成。消除回调地狱——优先于完成处理程序。
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 等待结果。取消是协作式的,而非强制的。
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 completionActor(线程安全)
Actor(Swift 5.5+)通过序列化访问来保护可变状态免受数据竞争。所有访问都通过 await 进行。用于共享状态时替代锁/队列。编译器验证安全性。
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 racesGCD(Grand Central Dispatch)
GCD 是传统的并发 API。DispatchQueue.global() 用于后台工作,.main 用于 UI 更新。QoS(.userInitiated、.background)确定任务优先级。对于非异步代码仍然有用。
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、服务器发送事件或任何随时间产生值的源。
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 { ... }协议深入
带关联类型的协议
关联类型(associatedtype)让协议声明一个占位类型,由遵循类型指定。这是 Swift 中协议泛型类型参数的等价物。类型从遵循类型的方法中推断。使用 'where' 子句约束关联类型。PAT(带关联类型的协议)不能直接用作存在类型,除非进行类型擦除或使用(Swift 5.7+)'any Container'。
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 的。
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'(不透明返回类型)。为性能优先使用泛型而非存在类型;需要异构集合时使用存在类型。
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 互操作仍然有用,但大多数模型优先使用结构体+协议。
// 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)中很常见。
// 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 */ }
}泛型深入
泛型函数与类型
泛型编写灵活、可复用的代码,适用于任何类型同时保持类型安全。T 是类型参数(占位符)。编译器为性能生成特化版本(无装箱)。泛型类型(Stack<T>)保持其类型参数。Element 是 Array 的泛型类型。泛型在编译时解析——无运行时开销,不同于存在类型。
// 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——无法进行操作。
// 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'。调用者可以使用协议方法但不能依赖特定类型。
// 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。
// 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 不那么必要,但对于存储/传递的错误仍然有用。
// 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()ARC 与内存管理
自动引用计数(ARC)
ARC(自动引用计数)管理类实例(引用类型)的内存。每个强引用增加计数;移除一个则递减。当计数为 0 时,对象立即释放(确定性的,不同于垃圾回收)。结构体/枚举(值类型)不使用 ARC——它们被复制。ARC 无法处理引用循环——你必须使用 weak/unowned 来打破它们。
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。
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。
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 是非逃逸的。
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+)在语言层面为共享可变状态强制执行独占性,从设计上消除数据竞争。
// 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 exclusivitySwiftUI 基础
视图与修饰符
SwiftUI 是 Apple 的声明式 UI 框架。视图是遵循 View 的结构体,具有 'body' 属性。修饰符(.font、.padding)返回新的包装视图——它们不会修改。视图是轻量级值类型;SwiftUI 对它们进行 diff 以更新实际 UI。声明式风格描述了给定状态下 UI 应有的样子,SwiftUI 处理过渡。
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。
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 接收。
// 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。
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。