为什么把 Swift 作为第一门语言

Swift 是苹果用来编写 iOS、macOS、watchOS 与 tvOS 应用的系统级语言。它把类型推断、泛型、协议扩展、值语义与结构化并发这些现代特性,和 Swift Package Manager、Xcode 预览器绑成一条完整工具链——你写完语法几乎立刻能看到界面,不必先花几周配环境。对初学者来说,这条「从代码到画面」的短路径非常关键,它让反馈循环缩短到几秒钟,而反馈速度直接决定了学习效率。

Swift 的门槛不在语法,而在「用类型把状态说清楚」;语法三天能过一遍,类型思维却决定了你后面几年写代码的手感。
核心知识点
  • 类型推断由右值推断类型,少写标注,但结果是静态确定的。
  • 值类型struct 与 enum 赋值即拷贝,天然规避共享可变状态。
  • 可选类型Optional 把「可能为空」写进类型,强制在编译期处理空值分支。
  • 协议面向协议编程用组合替代继承,是最重要的抽象手段。
Swift 语言入门配图:层叠的圆角卡片与抽象编程语言标记
值类型打底、协议做接口、界面声明式组合,这就是 Swift 的语感。

类型推断、值类型与可选值

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。后者还能限制最大并发数,这点在批量拉取列表时非常重要——不设上限的话,几百个请求同时打出去,很容易把自己的服务端打挂。