iOS 开发手册IOS DEVELOPMENT

CHAPTER 01语言基础 · LANGUAGE

Swift 语言基础

值类型安全、可选、协议、泛型与错误处理。掌握 Swift 的"安全默认"与"零成本抽象"思想,为并发、UI 与架构打下地基。

开始阅读 难度入门 先修任意编程语言 约2.5小时
01.1

Swift 的设计哲学

Swift 不是 Objective-C 的语法糖,而是一门围绕"安全默认"和"零成本抽象"重新设计的语言。理解它的三条主线,比背语法更重要。

主线一

安全默认

可选强制你显式处理"无值";变量默认不可变;数组越界、整数溢出在调试构建里会触发陷阱而非静默错误。

主线二

值类型优先

struct 与 enum 是默认选择,数据在传递时被拷贝(写时复制),天然避免共享可变状态带来的并发陷阱。

主线三

零成本抽象

协议、泛型、高阶函数在编译期被特化或内联,运行时没有额外开销,可以放心写出富有表现力的代码。

与 Objective-C 的关系

Swift 与 Objective-C 在同一 runtime 上共存,可以直接调用 Cocoa / UIKit 框架。新项目 99% 用 Swift;理解少量 Objective-C 互操作(如 @objc、dynamic)在接入老框架时有用,但不是入门重点。

01.2

值类型 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。

01.3

可选与可选链

可选(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 / ??。

01.4

协议与面向协议编程

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存在容器,可装任意符合者异构集合、可替换依赖
01.5

泛型

泛型让函数与类型在保持类型安全的同时复用逻辑。配合 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
    }
}
泛型 vs Any

Any 会抹掉类型信息、需要运行时强转,几乎总是下策。能用泛型表达的复用优先用泛型,保留编译期类型检查。

01.6

错误处理

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)
}
throws 不是异常

Swift 错误是显式的控制流,不会自动向上冒泡捕获所有类型。它不适合"绝不应当发生"的程序员错误(那种用 preconditionFailure / fatalError 更直接)。与并发章节的 async throws 配合使用时,错误会沿任务树传播。

01.7

内存语义与写时复制

值类型"拷贝"并不意味着每次赋值都深拷贝。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]
自己写的值类型何时需要 CoW

当你的 struct 内部持有 class 引用(如自定义缓冲区)且想保持值语义时,可在 mutating 方法里用 isKnownUniquelyReferenced 判断引用是否被独占,仅在不独占时复制。标准库类型已内置此优化。

01.8

常见陷阱与 FAQ

struct 里能放 class 吗?会有什么坑?

可以,但值语义会"泄漏":两个 struct 副本会共享同一个 class 实例,改一处影响另一处。要么把内部也改成值类型,要么明确这就是你想要的引用语义,并通过不可变设计(let + 内部不暴露 mutator)控制风险。

什么时候用 class 而不是 struct?

需要身份(比较"是不是同一个对象")、需要被 UIKit/框架持有(UIView、ViewController 等必须是 class)、需要继承共享行为、或需要显式共享可变状态(如全局缓存)时。其余情况默认 struct。

some 和 any 到底怎么选?

函数要返回"某一个确定的符合类型"用 some(性能更好、编译期确定);要装"任意符合者"的集合或字段用 any。SwiftUI 的 body 返回 some View 就是前者——它其实是某个具体 View 类型,只是对调用者隐藏。

01.9

实战练习与资源

以下练习按难度递增,全部完成后,你对 Swift 基础的掌握就达到了"能写进简历"的水平。

延伸资源