Swift 真的被搞得乱七八糟了吗?写了几年之后说点实话
最近跟几个 iOS 同行聊天,不止一个人吐槽:"Swift 这几年加的东西太多了,把一个好好的语言搞乱了"。翻社区也能看到类似的声音------有人说 Swift 正在变成"下一个 C++",有人说苹果根本不懂语言设计,每年 WWDC 就是往语言里硬塞新玩具。
我自己写 Swift 也有些年头了,从 2.0 版本到现在 6.x,亲眼看着它从一个"现代版 Objective-C"变成今天这个样子。要说没感受到膨胀是假的,但要说"被搞烂了"我又不太同意。这事儿比表面看起来复杂。
今天就聊聊这个话题。
膨胀是真的,先认个账
先别急着为 Swift 辩护。把这些年加进来的东西列一下,你就知道批评不是没道理:
并发这一整套 :async/await、actor、Sendable、@MainActor、structured concurrency、isolated 参数、nonisolated(unsafe)、region-based isolation、Swift 6 的完整 data race safety......
所有权模型 :borrowing、consuming、~Copyable、~Escapable、move-only types。
宏系统 :@freestanding、@attached,能在编译期生成代码。
Result builder:SwiftUI 那套 DSL 的底座。
Property wrapper :@State、@Published、@Observable、@Bindable......
泛型的演进 :opaque types(some)、existential types(any)、primary associated types、parameter packs、~Copyable 泛型约束。
各种 attribute :@MainActor、@preconcurrency、@retroactive、@unchecked Sendable......
写一个稍微复杂点的函数签名,可能长这样:
swift
func process<T>(_ items: some Collection<T>) -> any AsyncSequence<T, Error>
where T: Sendable & ~Copyable
每个关键词都有道理,但堆在一起确实让人喘不过气。尤其是从 Swift 3、4 那个时代过来的人,会明显感觉"这不是我认识的 Swift"。
这就是批评的源头。膨胀是客观事实,不用洗。
但为什么会变成这样?三个真实原因
骂完之后,我们得冷静想想:苹果为什么要这么做? 是他们真的不懂设计,还是有什么东西逼着他们必须加?
原因一:Swift 想同时当几种语言
这是最根本的问题。Swift 的野心比 Objective-C 大太多:
- 应用层语言(替代 OC,写 iOS/macOS app)
- 系统级语言(替代 C/C++,能写内核、嵌入式、驱动)
- 服务端语言(Vapor 那套)
- DSL 宿主(SwiftUI、Regex Builder、SwiftData)
一个语言要同时满足这四种场景,天然会膨胀。这不是 Swift 独有的命,C++ 是这样,Rust 正在走这条路。你想要"安全 + 高性能 + 表达力",只能不断加特性。
反例是 Go。Go 刻意不做这些------不要泛型(后来才加)、不要宏、不要所有权系统。结果是语言简洁了,但写应用层代码啰嗦、写 DSL 几乎不可能。简洁是用表达力换的,不是白来的。
苹果选了另一条路:我什么都要。代价就是你今天看到的 Swift。
原因二:并发和所有权的复杂度,是物理级的
很多吐槽 Swift 的人,把 Sendable、@MainActor、borrowing 这些东西当成"苹果瞎加的"。其实不是------这两块复杂度是本质的。
并发安全 :你想在编译期证明一段代码没有数据竞争,就必须引入 Sendable、actor、isolation domain 这些概念。Rust 靠 Send/Sync trait,Swift 靠 Sendable/@MainActor,两边复杂度其实差不多。
所有权 :你想消除 ARC 开销、支持 move semantics,就必须有 borrowing/consuming。Rust 这块甚至更复杂(还有 lifetime 标注)。
这是计算机科学的硬约束。 你不能既要"零成本抽象 + 内存安全 + 并发安全"又要"语法简单"。三者不可兼得。Swift 选了前者,就得付出后者的代价。
骂 Swift 这部分复杂,本质上是在骂并发编程本身复杂、系统编程本身复杂------那是真的复杂,不是苹果的锅。
原因三:向后兼容的累积
Swift 为了不把老项目全部搞挂,新特性往往是叠加 而不是替换:
any P和some P并存(existential vs opaque)async throws和老的 completion handler 并存@Observable宏和老的ObservableObject协议并存- 新的 typed throws 和老的
throws any Error并存
所以你学 Swift 现在要学两套做同一件事的方法,一套给老代码,一套给新代码。
这是所有长寿语言的通病。看看 C++ 的 auto_ptr → unique_ptr、Java 的 Date → Calendar → java.time、Python 的 % 格式化 → .format() → f-string。能用十年的语言,没一个不膨胀的。 因为你不能扔掉已经写好的几十亿行代码。
哪些批评我是真的同意
但是,别以为我是全盘为 Swift 辩护。有些膨胀确实是设计问题,不是物理约束:
并发关键词太多,语义重叠
@MainActor、@preconcurrency、@Sendable、nonisolated、isolated、@unchecked Sendable------同一件事(并发隔离)用了太多关键词,很多组合语义要反复查文档才能确认。
这个完全可以设计得更简洁。Swift 团队在这块明显是分多次迭代上的,每次加一点,最后拼起来就显得零碎。
泛型语法越来越读不懂
还是上面那个例子:
swift
func process<T>(_ items: some Collection<T>) -> any AsyncSequence<T, Error>
where T: Sendable & ~Copyable
每个部分分开都能解释清楚,但组合起来的认知负担极大。Swift 在"表达力"和"可读性"之间,明显偏向了前者。这是设计选择,不是必然的。
对比 Kotlin、Go,它们的泛型同样够用,但没搞到这种程度。
SwiftUI 的魔法太多
@State、@Binding、@Environment、@Observable、@Bindable------property wrapper 解决的问题是真的(双向绑定、依赖追踪、环境传递),但新手要理解一个 SwiftUI view 为什么会自动刷新,得先懂一整套元编程。
这跟 React hooks 的问题很像:隐藏了太多控制流,写起来爽,debug 起来哭。SwiftUI 一个预览不工作,你可能要排查十几个可能原因。
编译器错误信息依然很烂
这是 Swift 被吐槽最久的点,十年了没有根本改善。泛型或 SwiftUI 出错时,编译器报错经常几十行,指向一个根本不相关的地方。经典的 error: type of expression is ambiguous without more context,写 Swift 的人看到这句话估计都有 PTSD。
更公允的说法
综合上面的分析,我觉得对 Swift 的评价应该是这样:
Swift 的膨胀 = 必然代价 + 部分设计失误
必然代价那部分(并发、所有权、泛型演进),骂它"搞乱"其实是骂计算机科学本身。选哪条路都有代价,Rust 更复杂,C++ 更混乱,Kotlin 为了简单牺牲了一些能力。
设计失误那部分(关键词过多、语法噪音、错误信息),骂得对。Swift 团队在功能添加 上很激进,在清理和简化上很保守------一年一个 WWDC 逼着不断出新,但很少见他们下决心废掉一个老语法。
还有一个更重要的观察:
Swift 不是一个语言,是一个语言的多个层级。
- 写日常 app 业务代码,其实依然简洁、现代、愉快(这是 90% 人的使用场景)
- 写库代码、框架代码、性能敏感代码,才会遇到所有膨胀的复杂度(这是 10% 人的场景)
- 吐槽声最大的人,很多是看到了所有特性的文档和 WWDC session,但实际写代码 80% 特性用不到
这跟 C++ 特别像:C++ 的复杂度是真的,但写应用层 C++ 不需要懂模板元编程。Swift 也一样------你用到哪层就承担哪层的复杂度。
真正的问题是 Swift 对"多层级 "这件事说得不够清楚,没有明确告诉开发者"你写 app 可以忽略 A/B/C,只有写库才需要 D/E/F"。WWDC 一股脑把新特性全扔出来,文档也不分层次。结果就是所有人看到满屏 @ 和 ~ 就觉得这语言疯了。
所以到底该不该学 Swift?
经常有人私信问我这个问题,尤其是想入 iOS 的新人。我的回答一直是:
该学,但别被吓到。
- 如果你只是写 app,Swift 依然是现代化、好用的语言。SwiftUI 上手成本比 UIKit 低。那些
@unchecked Sendable之类的玩意儿,你基本用不到。 - 如果你想深入做框架、做库、做性能优化,那你就得逐步攻克那些复杂特性------但这是任何系统级语言都需要的功夫,不是 Swift 独有的。
- 真正的难点其实不是语法,是理解苹果生态的一堆约定:runloop、main thread、Combine、Concurrency、SwiftUI 的生命周期......这些比语言本身更花时间。
至于学习方法,我给一个实在的建议:不要一口气把所有特性文档看完。没用,看完就忘。
正确的路径是:遇到问题再查,查的时候顺便问清楚来龙去脉 。比如你写 SwiftUI 遇到 @Observable 报错,就借机把 property wrapper 和 Observation 框架的演进搞清楚;你写 async 代码遇到 data race 警告,就借机理解 Sendable 和 actor isolation。
这种"问题驱动"的学习最扎实。
写到最后
吐槽归吐槽,Swift 依然是我这几年写得最多、也最顺手的语言。它有毛病,但没被搞坏。
苹果团队面对的约束其实比旁观者想象的多得多------既要保留旧代码兼容,又要追赶 Rust 在系统编程上的优势,还要服务 SwiftUI 那种 DSL 的表达需求,最后还要对付亿万普通开发者。做成现在这样,说实话已经不容易了。
对一门工业语言最公平的评价,永远不是拿它和教科书里的"理想语言"比,而是拿它和它的同代人比。Swift 比同时期的 Kotlin 表达力强,比 Rust 好学,比 Go 功能全,比 C++ 安全。它的膨胀是这些优势的代价。
顺便说一下,写这篇文章的过程中我又把几个 Swift 新特性的 proposal 翻了一遍(swift-evolution 那个仓库),很多设计背后的讨论其实挺有意思。如果你对某个特性"为什么要这样设计"好奇,强烈推荐去翻原始 proposal,比 WWDC 的宣传片信息量大得多。
平时我遇到这类"某个 API 为什么要这么设计"、"这个新特性值不值得用"的问题,习惯打开 www.gufacode.com 顺手聊两句再决定。不是让它给标准答案,是当个陪聊对象把思路过一遍------有时候问题本身想清楚了,答案也就自己出来了。
就这样,有不同意见欢迎评论区拍砖。