为什么把 Swift 作为第一门语言
Swift 是苹果用来编写 iOS、macOS、watchOS 与 tvOS 应用的系统级语言。它把类型推断、泛型、协议扩展、值语义与结构化并发这些现代特性,和 Swift Package Manager、Xcode 预览器绑成一条完整工具链——你写完语法几乎立刻能看到界面,不必先花几周配环境。对初学者来说,这条「从代码到画面」的短路径非常关键,它让反馈循环缩短到几秒钟,而反馈速度直接决定了学习效率。
Swift 的门槛不在语法,而在「用类型把状态说清楚」;语法三天能过一遍,类型思维却决定了你后面几年写代码的手感。
- 类型推断由右值推断类型,少写标注,但结果是静态确定的。
- 值类型struct 与 enum 赋值即拷贝,天然规避共享可变状态。
- 可选类型Optional 把「可能为空」写进类型,强制在编译期处理空值分支。
- 协议面向协议编程用组合替代继承,是最重要的抽象手段。
类型推断、值类型与可选值
let 声明常量、var 声明变量,大部分场景不必写类型,但推断结果是编译期固定的,赋错类型直接报错。值类型是另一条主线:struct 与 enum 赋值时复制,class 才是引用语义。多线程环境下,值类型天然安全——每个线程拿到的是自己的副本,不需要加锁,这也是 Swift 推荐优先使用 struct 的根本原因。
可选值的写法决定了代码质量。guard let 做前置校验并提前返回,if let 做局部解包,?? 给默认值;尽量不用 ! 强解包,它把编译期建立的安全网撕开一道口子,空值一旦出现就是崩溃。举个具体的例子:航班延误时间写成 var delayMinutes: Int?,就能把「延误时间未知」这个业务事实直接写进类型;如果它是非可选的 Int,未知状态就无处安放,你只能拿 -1 之类的哨兵值糊弄,后续每个判断分支都要额外考虑这个特例,代码会越来越脏。
闭包与协议:把行为交给调用方
闭包是可以被传递的函数体,参数类型、返回类型甚至参数名都能省略(用 $0 引用第一个参数)。真正需要留意的是捕获语义:闭包默认强引用捕获外部变量,一旦被放进长期持有的存储,就会形成循环引用,必须在捕获列表里写 [weak self] 打断这条链。
protocol Cacheable {
var cacheKey: String { get }
}
struct Article: Cacheable {
let id: Int
var cacheKey: String { "article-\(id)" }
}
func store<T: Cacheable>(_ items: [T], using save: (T) -> Void) {
items.filter { !$0.cacheKey.isEmpty }.forEach(save)
}
store([Article(id: 1)]) { print("已缓存:\($0.cacheKey)") }
这段代码示范了三件事:协议只声明能力,泛型约束负责复用,闭包把「怎么存」的决定权交给调用方。当你写出一连串 if type == .xxx 的分支时,通常意味着该抽一个协议出来了。
用枚举与模式匹配表达状态
Swift 的枚举可以携带关联值,配合 switch 做穷尽匹配。相比若干个布尔标志位,枚举的 case 互斥,非法组合根本写不出来,编译器还会逼你处理全部分支。这在界面状态、网络请求结果这类「多选一」的场景里尤其有价值。
| 表达方式 | 非法状态 | 编译期检查 |
|---|---|---|
| 多个 Bool 标志 | 可能出现 | 无 |
| 字符串状态 | 可能拼错 | 无 |
| 关联值枚举 | 不可能 | switch 穷尽性 |
另一个值得坚持的习惯是:switch 里不要写 default。以后新增 case 时,编译器会在每一处 switch 上报错,这份报错清单就是免费的重构指引。
并发模型:async/await 与结构化并发
Swift 5.5 起提供了 async/await、Task 与 actor。异步代码像同步代码一样线性书写,由编译器在 await 处插入挂起点:那个位置就是可能让出线程、被别人插队的地方。理解这一点,才能解释为什么不要在挂起点前后假设共享状态没变。
下面是一个完整的网络请求示例,包含状态码校验与错误抛出:
struct User: Decodable { let id: Int; let name: String }
func fetchUser(id: Int) async throws -> User {
let url = URL(string: "https://example.com/api/users/\(id)")!
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode(User.self, from: data)
}
要并发拉取多个资源,用 async let 或 withTaskGroup。后者还能限制最大并发数,这点在批量拉取列表时非常重要——不设上限的话,几百个请求同时打出去,很容易把自己的服务端打挂。
新手最常见的几个坑
一是用 ! 强解包代替判空,崩溃日志里满是「unexpectedly found nil」。二是在 struct 里写可变方法却忘记标注 mutating,编译报错时容易误以为是语法写错了。三是把闭包捕获的 self 直接塞进长期持有的属性,造成循环引用,内存只涨不降。四是在 Task 里更新 UI 却忽略 @MainActor 约束,界面偶发不刷新。五是给 async 函数套一层 Task { } 后用 try? 顺手吞掉错误,调用方永远不知道失败了。
排查顺序建议是:先看编译器警告——Swift 的警告往往就在提示你写得不安全;再看 Xcode 的 Issue Navigator 定位问题文件;最后才上断点与 LLDB 的 po 命令打印值。八成问题在编译阶段就已经被指出了,跳过前两步直接用调试器,通常只是浪费时间。
还有一个小陷阱:把 async 函数写成属性访问器或者在 body 里直接调用。正确做法是在视图出现时用 .task 修饰符启动异步工作,它会在视图消失时自动取消任务,避免请求回来时视图已经不存在。
学习路径与延伸阅读
推荐推进顺序是:语法与可选值 → 声明式界面与状态绑定 → 工程结构与上架流程 → 平台特有能力。每个阶段配一个能跑起来的小项目,比刷完整本教程更有效,因为 Swift 的很多细节只有在真实项目里才会暴露。
- 继续读《SwiftUI 声明式 UI:从零构建界面》,把语法转成真正的界面。
- 再看《iOS 应用开发全流程:从 Xcode 到上架》,把项目变成能发布的应用。
- 想深入并发与内存,去读 Swift 官方博客里关于 actor 与 Sendable 的系列文章。
- 要做桌面应用时读《macOS 桌面应用开发入门》,同一套语言能力可以复用。