Swift 的设计哲学
Swift 不是 Objective-C 的语法糖,而是一门围绕"安全默认"和"零成本抽象"重新设计的语言。理解它的三条主线,比背语法更重要。
安全默认
可选强制你显式处理"无值";变量默认不可变;数组越界、整数溢出在调试构建里会触发陷阱而非静默错误。
值类型优先
struct 与 enum 是默认选择,数据在传递时被拷贝(写时复制),天然避免共享可变状态带来的并发陷阱。
零成本抽象
协议、泛型、高阶函数在编译期被特化或内联,运行时没有额外开销,可以放心写出富有表现力的代码。
Swift 与 Objective-C 在同一 runtime 上共存,可以直接调用 Cocoa / UIKit 框架。新项目 99% 用 Swift;理解少量 Objective-C 互操作(如 @objc、dynamic)在接入老框架时有用,但不是入门重点。
值类型 vs 引用类型
这是 Swift 最重要的心智模型分水岭。简单说:struct/enum 是值类型(拷贝语义),class 是引用类型(共享语义)。选错会直接影响正确性与并发安全性。
| 维度 | struct / enum(值) | class(引用) |
|---|---|---|
| 赋值/传参 | 拷贝一份独立副本 | 共享同一实例(引用) |
| 可变性 | 仅 var 时本身可变,但副本互不影响 | 多处持有同一对象,一处改动全局可见 |
| 继承 | 不支持(用协议组合替代) | 支持单继承 |
| 内存 | 通常栈上,自动释放 | 堆上,由 ARC 管理 |
| 默认线程安全 | 天然安全(无共享) | 需自行加锁或用 actor |
struct Point { var x: Int; var y: Int } // 值类型
class Node { var value: Int; var next: Node? } // 引用类型
var a = Point(x: 1, y: 2)
var b = a
b.x = 99
print(a.x) // 1 —— a 与 b 互不影响
let n1 = Node(value: 1, next: nil)
let n2 = n1
n2.value = 42
print(n1.value) // 42 —— 共享同一对象
默认用 struct;需要身份(identity,即"是同一个东西")、需要继承、需要引用语义(如共享缓存)时才用 class。模型数据、配置、请求参数几乎总是 struct;UIView / ViewController 等框架类型由框架决定必须是 class。
可选与可选链
可选(Optional)把"可能没有值"编码进类型系统。Optional<T> 本质是 enum { none; some(T) },既有的 nil 不再是魔法,而是枚举的一个 case。
var name: String? = nil // 可能没有值
// 1. if let 解包(成功进入作用域,得到非可选 name)
if let n = name {
print("hello \(n)")
}
// 2. guard let 提前退出(常用于函数入口校验)
func greet(_ input: String?) {
guard let n = input else { return } // 失败时尽早返回
print("hello \(n)") // 此处 n 已是非可选
}
// 3. 空合并运算符 ??
let display = name ?? "匿名" // name 为 nil 时用默认值
// 4. 可选链 ?.
let street = user?.address?.street // 任意一环为 nil,整条返回 nil
// 5. map / flatMap 转换
let upper = name.map { $0.uppercased() } // String? -> String?
name! 在值为 nil 时直接崩溃。只在你能 100% 保证非空的极少数场景(如 @IBOutlet 已连接)使用。常态下优先用 if let / guard let / ??。
协议与面向协议编程
Swift 偏好用协议组合行为,而不是深继承。协议定义能力契约,协议扩展提供默认实现,附加约束的 where 子句可做条件默认实现。
protocol Loggable {
var id: String { get }
func log()
}
// 协议扩展提供默认实现
extension Loggable {
func log() { print("[\(type(of: self))] \(id)") }
}
struct Order: Loggable {
let id: String
// log() 直接用协议扩展的默认实现
}
// 带 where 约束的默认实现
extension Collection where Element: Loggable {
func logAll() { forEach { $0.log() } }
}
关联类型与 some / any
协议带关联类型或 Self 要求时不能直接当变量类型,需用两种存在容器:
protocol Shape { associatedtype Area; var area: Area { get } }
// some:单一具体类型,编译期已知(不透明返回类型)
func makeTriangle() -> some Shape { Triangle() }
// any:运行时可以是任意符合类型(存在容器,有少量开销)
var shapes: [any Shape] = [Triangle(), Circle()]
| 关键字 | 含义 | 典型用途 |
|---|---|---|
some Protocol | 不透明返回类型,底层是某一个确定类型 | 函数返回值、View 的 body |
any Protocol | 存在容器,可装任意符合者 | 异构集合、可替换依赖 |
泛型
泛型让函数与类型在保持类型安全的同时复用逻辑。配合 where 子句可在约束下提供更强能力。
// 泛型函数
func first(_ items: [T]) -> T? { items.first }
// 泛型类型
struct Stack {
private var items: [Element] = []
mutating func push(_ e: Element) { items.append(e) }
mutating func pop() -> Element? { items.popLast() }
}
// where 约束:只有 Equatable 元素才提供去重
extension Stack where Element: Equatable {
var unique: [Element] {
var seen: [Element] = []
for e in items where !seen.contains(e) { seen.append(e) }
return seen
}
}
Any 会抹掉类型信息、需要运行时强转,几乎总是下策。能用泛型表达的复用优先用泛型,保留编译期类型检查。
错误处理
Swift 用 throws 表达可恢复错误,用 Result 把成功/失败封装成值,用 fatalError 处理不可恢复的程序员错误。
enum NetworkError: Error {
case invalidURL
case http(code: Int)
case decodingFailed(Error)
}
func fetch() throws -> Data {
throw NetworkError.invalidURL // 抛出
}
// try / try? / try!
do {
let data = try fetch()
} catch NetworkError.invalidURL {
print("URL 错了")
} catch {
print("其他错误:\(error)") // error 是兜底绑定
}
// try? 把错误转成 nil,忽略细节
let data = try? fetch()
// Result:把结果当值传递,适合回调场景
let result: Result = .failure(.invalidURL)
switch result {
case .success(let d): print(d.count)
case .failure(let e): print(e)
}
Swift 错误是显式的控制流,不会自动向上冒泡捕获所有类型。它不适合"绝不应当发生"的程序员错误(那种用 preconditionFailure / fatalError 更直接)。与并发章节的 async throws 配合使用时,错误会沿任务树传播。
内存语义与写时复制
值类型"拷贝"并不意味着每次赋值都深拷贝。Swift 的 Array、Dictionary、String 等采用写时复制(Copy-on-Write):多个变量共享同一份存储,只有某一侧发生修改时才真正复制。
var a = [1, 2, 3]
var b = a // 此刻与 a 共享底层存储
b.append(4) // 仅此刻 b 才被复制,a 不受影响
print(a) // [1, 2, 3]
print(b) // [1, 2, 3, 4]
当你的 struct 内部持有 class 引用(如自定义缓冲区)且想保持值语义时,可在 mutating 方法里用 isKnownUniquelyReferenced 判断引用是否被独占,仅在不独占时复制。标准库类型已内置此优化。
常见陷阱与 FAQ
可以,但值语义会"泄漏":两个 struct 副本会共享同一个 class 实例,改一处影响另一处。要么把内部也改成值类型,要么明确这就是你想要的引用语义,并通过不可变设计(let + 内部不暴露 mutator)控制风险。
需要身份(比较"是不是同一个对象")、需要被 UIKit/框架持有(UIView、ViewController 等必须是 class)、需要继承共享行为、或需要显式共享可变状态(如全局缓存)时。其余情况默认 struct。
函数要返回"某一个确定的符合类型"用 some(性能更好、编译期确定);要装"任意符合者"的集合或字段用 any。SwiftUI 的 body 返回 some View 就是前者——它其实是某个具体 View 类型,只是对调用者隐藏。
实战练习与资源
以下练习按难度递增,全部完成后,你对 Swift 基础的掌握就达到了"能写进简历"的水平。