☰
Swift生态的变与不变:并发与状态管理的实践观察
2026/10/23 15:58:35 网站建设 项目流程

第 121 期周报,看到这个数字的时候我自己也愣了一下。每周整理几条 Swift 生态里值得关注的内容,这件事不知不觉就坚持到了三位数。这期标题里借用了莫奈那套著名的《干草堆》——他对着同一堆干草画了几十遍,晨光、暮色、冬雪、夏雾,光影一直在变,草堆却始终立在那里。Swift 世界给我的感觉差不多也是这样:六月的 WWDC 一过,新特性就像新的光线一样打下来,把整个生态照得闪闪发亮;可语言的根、社区做事的方式、写代码时最核心的那些理念,其实一直没动过。

所以这一期我不打算做逐条更新罗列,而是借着"变幻的光影,不变的干草堆"这个意象,聊聊我对 Swift 生态里"变与不变"的一些观察,顺便把我最近在项目里做并发迁移、SwiftUI 状态管理重构的真实过程翻出来,把步骤、取舍和踩过的坑都摊开讲。不管你是刚接触 Swift 的新手,还是已经在生产环境里滚了几年的老手,这篇文章应该都能给你一点参考。

1. 写在 #121 期之前:变化的光影,不变的干草堆

1.1 为什么要用莫奈的干草堆来理解 Swift 生态

莫奈在 1890 年到 1891 年间画了大约三十幅《干草堆》,同一个题材,从秋天画到春天,反复捕捉光线在草堆表面留下的痕迹。评论家说,莫奈真正想画的不是草堆本身,而是"光线的瞬间感受"。但有趣的是,如果没有那堆实实在在的干草作为锚点,那些光影变化就失去了意义。

我对 Swift 这十年的观感很接近这个状态。Swift 1.0 时代我们还在跟 optional 和访问控制搏斗,后来陆续等来了 protocol extension、泛型改进、SwiftUI、async/await、宏、严格并发检查。每一年都有新东西落到 Xcode 的 Release Notes 里,给人的感觉就是光影一直在变,快到你追都追不过来。可如果拉开时间线,你会发现那些真正支撑起这个语言的底层主张——内存安全、值语义、协议导向、编译期兜底——几乎没有动摇过。

写周报最怕的就是变成"新特性新闻联播",今天发一个新框架,明天出一个新 API,每篇都是"哇这个东西好厉害",读者看完却没有留下任何结构性的认识。所以我一直在想怎么给技术内容加一个"锚点"。到了第 121 期,我觉得"干草堆"这个意象特别适合用来做这一期的结构,一方面说说那些正在变化的前沿,另一方面也回头看看那些长期稳定的内核,两边对照着看,很多困惑反而能解开。

1.2 这一轮光影:WWDC 之后值得盯紧的几条线

每年六月的 WWDC 都是 Swift 生态一次集中的"打光时刻"。今年观察下来,我自己的判断是:对大部分 Swift 开发者来说,真正值得盯紧的不是某个炫酷的新组件,而是下面这几条线和你的日常工作直接相关。

第一条是并发模型的全面收紧。Swift 5.5 引入 async/await 是个分水岭,后面几个版本一直在完善 actor、Sendable、全局隔离这些概念。到 Swift 6 这一代,严格并发检查变成默认行为,数据竞争会在编译期被大量拦截,这等于把并发安全从"运行时祈祷"变成了"编译器把关"。对这个变化,不同项目的感受会差很多:新项目从第一天就用 Swift 6 语言模式,顺风顺水;老项目则要面对一大堆历史代码的迁移问题,这个过程非常考验对隔离边界的理解。

第二条是 SwiftUI 状态管理的收敛。从早期一堆属性包装器,到后来引入宏,苹果把 UI 框架的数据流往"更少关键字、更多编译器保证"的方向推。你看 @State、@Observable、@Environment 这些 API 的演进,本质上都在做同一件事:让状态来源单一、变化路径清晰。

第三条是 Swift 跨平台边界的扩大,服务端、嵌入式、命令行工具,甚至 Windows 和 Linux 上的支持都在往前推进。对苹果生态内的开发者来说,这可能暂时不是主线,但它意味着同一套语言能力可以复用到更多场景,对长期职业规划是个值得留意的变量。

后面几节我会把这三条线展开,分别讲清楚"变的到底是什么、不变的是什么、以及实际操作中怎么应对"。

2. 光影一:Swift 并发改造,怎么一步步迁移过去

2.1 从 async/await 到严格并发,变的其实是表达方式

Swift 在 async/await 出现之前,异步代码主要靠闭包回调来传递结果。逻辑简单的时候问题不大,一旦出现"先请求 A,再用 A 的结果请求 B,最后把 B 的结果刷新到界面"这种链条,代码就会迅速膨胀成嵌套的回调结构,也就是大家常说的"回调地狱"。我自己维护过一个比较老的项目,一个登录流程里嵌套了四层闭包,改一个参数要顺着闭包一路看下去,眼睛都能看花。

async/await 解决的是"表达方式"的问题。它把异步流程重新拉平,让代码看上去像同步代码一样顺。你可以这样写:

func loadUserAndPosts() async throws -> (User, [Post]) { let user = try await fetchUser() let posts = try await fetchPosts(for: user.id) return (user, posts) }

对比之前嵌套闭包的写法,这段代码的阅读顺序就是执行顺序,调试时打断点也直观得多。但我要特别强调一点:async/await 只是"表面上"把代码拉平了,底层的并发执行模型并没有变简单。线程切换、任务调度、数据竞争这些老问题依然存在,只是换了一种形式出现。

到了 Swift 6,变化进一步加深。编译器默认启用严格并发检查,它会盯着你的类型和数据流,不允许你在没做隔离的情况下跨 actor 随意访问可变状态。换句话说,以前一些"跑起来好像没问题、偶尔崩溃一次"的并发 bug,现在直接变成了编译错误。这在一开始会让很多老项目炸出一堆报错,但从长期的工程质量角度看,这个演进方向是对的——它把安全从运行时前移到了编译期,这正是 Swift"安全优先"哲学在并发领域的延伸。

2.2 迁移实录:把回调式网络模块改写成 Swift Concurrency

理论说再多不如动手改一段代码。我最近把一个旧项目里的网络层模块从闭包回调改造成 Swift Concurrency,整个过程很典型,我拆开来一步步说。

改造之前,模块长这样:

enum APIClient { static func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) { let url = URL(string: "https://api.example.com/users/\(id)")! URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { completion(.failure(error)) return } guard let data = data else { completion(.failure(APIError.emptyData)) return } do { let user = try JSONDecoder().decode(User.self, from: data) completion(.success(user)) } catch { completion(.failure(error)) } }.resume() } }

这段代码的问题是:调用者必须手动管理闭包的生命周期,还要处理两层嵌套的错误分支。如果调用方在闭包里又发起下一个网络请求,回调嵌套就开始了。

第一步,先把它改成 async 版本,接口保持不变:

enum APIClient { static func fetchUser(id: Int) async throws -> User { let url = URL(string: "https://api.example.com/users/\(id)")! let (data, response) = try await URLSession.shared.data(from: url) guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else { throw APIError.badStatus } return try JSONDecoder().decode(User.self, from: data) } }

这里我做了一个很小的设计取舍:把状态码检查也纳入进来,而不是像旧版本那样只判断 data 是否为空。原因是 async/await 版本代码更短,有空间把网络层的通用校验逻辑统一收敛,省得每个调用方自己判断 response。

第二步,关注并发安全。如果 APIClient 需要维护缓存或者请求去重逻辑,那它必须是一个有状态的类型。这种情况下我会把它声明成 actor,让编译器来保证对内部状态访问的隔离:

actor APIClient { private var cache: [Int: User] = [:] func fetchUser(id: Int) async throws -> User { if let cached = cache[id] { return cached } let url = URL(string: "https://api.example.com/users/\(id)")! let (data, response) = try await URLSession.shared.data(from: url) guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else { throw APIError.badStatus } let user = try JSONDecoder().decode(User.self, from: data) cache[id] = user return user } }

actor 的好处是它的隔离规则由编译器强制:你在 actor 外部访问 cache 是直接编译报错的,必须通过 actor 内部方法才能操作。这就是"隔离边界"的实体化——不再靠程序员自觉,而是靠类型系统兜底。

第三步,处理调用方。原来的 ViewModel 里,调用代码是:

APIClient.fetchUser(id: id) { result in switch result { case .success(let user): self.user = user case .failure(let error): self.errorMessage = error.localizedDescription } }

改成 async/await 后是:

do { let user = try await client.fetchUser(id: id) self.user = user } catch { self.errorMessage = error.localizedDescription }

改动量不大,但阅读体验差距明显。调用方的 await 会自动把后续代码调度到合适的执行上下文,UI 层不需要手动切回主线程,这背后是 MainActor 在起作用。如果你在 View 或 ViewModel 上标注了 @MainActor,那么 await 回来之后的代码默认就在主线程执行。

2.3 迁移过程中的心态与边界:别拿着一把小刀去砍大树

迁移最忌讳的就是"一把梭"。async/await 有传染性:一个函数变成 async,调用它的函数大概率也要变成 async,一路往上传染,可能波及整个模块。如果你的项目有几万行老代码,想在一个版本里全部切成 Swift 6 语言模式,很容易出现几百个编译错误,然后整个团队陷入漫长的修错循环。

我的做法是分批切。新写的代码直接上 async/await,老代码在维护到的时候顺手改,不为了迁移而迁移。同时打开 Xcode 里的"并发检查"增量开关,从 Swift 5 语言模式逐步过渡到 Swift 6 语言模式,每切一个模块,就把这个模块里的编译错误消干净,再切下一个。

这里有一个技术细节值得单独说:并发迁移不是简单的"把 callback 改成 async"。真正难的是找出哪些类型需要在 actor 之间传递、哪些类型需要标 Sendable。比如你有一个自定义的模型类,里面有几个 var 属性,它在两个 actor 之间传的时候编译器会报错,提示类型不符合 Sendable。这种报错不是让你删掉代码,而是在提醒你:这个类型的设计在并发环境下不安全。如果这个模型确实需要跨隔离域传递,可以考虑把它改成 struct,或者把属性改成不可变(let),再或者用 actor 封装可变部分。

我把这个决策过程总结成一张表,方便你对照:

场景推荐做法原因
纯数据结构,无共享可变状态用 struct + let 属性,自动满足 Sendable值拷贝,天然并发安全
有共享可变状态,且跨任务访问用 actor 封装编译器强制隔离,避免数据竞争
旧代码太多,暂时无法整体迁移保持 Swift 5 语言模式,新代码用 async渐进改造,控制风险
回调与 async 必须混用用 withCheckedThrowingContinuation 桥接保留现有接口,逐步替换调用方

实战下来,我最大的体会是:迁移不是重写,是"厘清边界"。你越清楚每个类型的状态归属、每个对象在哪个隔离域里存活,迁移就越顺。与其抱怨编译器报错多,不如把它当成一次免费的并发审查。

3. 光影二:SwiftUI 状态管理,从属性包装器到宏的转身

3.1 状态管理为什么是 SwiftUI 项目最容易被绕晕的地方

SwiftUI 上手容易,但状态管理一直是争议最多的区域。早期版本里,一个简单的数据模型可能要同时用到 @State、@ObservedObject、@StateObject、@EnvironmentObject 好几种属性包装器,配合 ObservableObject 协议和 @Published 属性,还要引入 Combine 框架。对一个新手来说,光搞清楚"什么时候该用哪个"就够喝一壶的。

我见过不少团队项目,状态管理方案五花八门,有的页面用 @StateObject,有的用 @ObservedObject,还有的干脆把数据全放全局单例里,最后的结果就是 bug 出现时没有任何规律,排查像大海捞针。这不是开发者不努力,而是旧方案本身太容易出错了:属性包装器太多,职责边界模糊,中间还有 Combine 的订阅生命周期在暗处作祟。

苹果后续的解决思路很清晰:引入宏(Macro),把"观察"这个能力从 Combine 手里拿回来,变成编译器层面的自动生成。这就是 @Observable 宏登场的背景。它让一个普通类具备可观察能力,却不强制你继承 ObservableObject,也不需要 @Published 来标记属性,定义变得干净很多。这个变化的本质是减少模板代码、统一数据流,"变更可观察"这件事由编译器兜底,人脑需要记的东西就少了。

3.2 用 @Observable 重构一个计时器页面的完整过程

拿一个简单的计时器页面举例。旧方案下的代码是这样:

final class TimerModel: ObservableObject { @Published var count: Int = 0 private var timer: Timer? func start() { timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in self?.count += 1 } } } struct TimerView: View { @StateObject private var model = TimerModel() var body: some View { Text("\(model.count)") Button("开始") { model.start() } } }

旧方案问题在哪?第一,TimerModel 必须继承 ObservableObject,引入 Combine 的发布语义。第二,@StateObject 和 @ObservedObject 的区别需要自己理解透彻,搞错生命周期就等着看界面不刷新的诡异问题。

用 @Observable 重构之后:

@Observable final class TimerModel { var count: Int = 0 private var timer: Timer? func start() { timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in self?.count += 1 } } } struct TimerView: View { @State private var model = TimerModel() var body: some View { Text("\(model.count)") Button("开始") { model.start() } } }

改动看起来不大,但语义清晰了很多。@State 直接持有模型对象,模型里的属性默认就被观察,不需要 @Published;类也不需要继承 ObservableObject,不依赖 Combine。代码量减少,心智负担降低,出错的概率自然也就小了。

这里有个细节想提醒你:@Observable 配合 @State 使用时,如果你在 body 里写 Text("(model.count)"),SwiftUI 会正确追踪 count 属性的读取,count 变化时刷新视图;但如果你把 model.count 传给一个子视图的普通属性,子视图不会自动更新,因为它没有形成依赖追踪。实际遇到这种情况,可以考虑用 @Bindable 获取绑定,或者把数据访问放在需要响应的视图层级里。

3.3 原地踩坑:预览不刷新、环境丢失、导航状态重置的排查思路

SwiftUI 状态管理的坑,很多都是"结构对了但细节没对"导致的。我整理了几个高频问题,每个都来自真实项目的现场排查。

第一个是预览不刷新。最常见的原因是 Preview 里没有正确注入环境依赖。比如你的视图在 body 里用了 @EnvironmentObject,但 Preview 代码里忘了加 .environmentObject(...),那预览就会崩溃或者白屏。用 @Observable 之后,环境注入的方式也要相应调整,在视图上通过 .environment(_:model) 传入。很多新项目踩坑是因为网上旧教程还在教 @EnvironmentObject 的用法,代码混用之后预览就炸了。

第二个是导航状态与数据不同步。NavigationStack 的 path 如果绑定到一个数组,这个数组的生命周期必须跟视图保持一致。我见过有人把 path 放在普通 let 属性里,结果导航栈怎么点都不推进;也有人把 path 放到全局单例里,结果切到后台再回来,导航栈被系统还原,单例里的数组却已经变了。排这类问题,第一件事是确认 path 的真实持有者是谁,是不是跟视图生命周期对齐。

第三个是旧组件和新观察宏混用带来的警告。一个 @Observable 对象如果同时又被包装成 ObservableObject 去兼容旧代码,Xcode 会在运行时输出警告,告诉你这种用法会退化为旧的观察机制。这种"兼容性"看似方便,长期看会掩盖问题,建议尽快统一到新方案。

我实操中还有一个体会:状态管理出问题的时候,不要急着在视图层打补丁,先画一下"数据从哪来、单向流怎么走"。大部分卡壳都是因为有人在某个中间层直接修改了不属于自己的状态,破坏了单向数据流。这个原则从 SwiftUI 1.0 到现在的版本都没变过,属于"干草堆"级别的稳定性。

4. 光影三:Swift 的领地正在扩大,但脚手架没变

4.1 从苹果生态走向服务端与嵌入式,Swift 在撕掉"iOS 专属"标签

Swift 刚出来那几年,大家默认它是"写 iOS App 的语言",出了苹果生态基本没人关心。这几年情况明显变了。Swift 在 Linux 和 Windows 上有了官方支持,服务端框架(比如 Vapor、Hummingbird)也越来越成熟,一些公司已经用 Swift 写后端 API,跟客户端共享一套模型代码。今年的新动态里,Swift 在嵌入式方向也有布局,这意味着将来你车里、家电里,可能也有跑着 Swift 的逻辑。

这件事对普通开发者的影响有多大?我的看法是:不必急着换赛道,但值得留意。服务端和嵌入式对语言特性的需求,反过来会推动 Swift 核心能力的进化。比如更严格的并发检查、更小的运行时、更高效的编译优化,这些改进最终都会让苹果生态内的 App 开发受益。所以这是一场"光影"的变化:Swift 的适用场景在变多,但它解决问题的基本范式——类型安全、协议抽象、值语义——没有变,脚手架还是那一套。

如果你对服务端感兴趣,建议用一个小项目切入:比如写一个返回 JSON 的 REST API,跑在本机 8080 端口,用 SwiftPM 管理依赖。这个过程能帮你理解 Swift 在非 UI 场景下的表现,也会让你对并发、生命周期这些概念有更深的理解。

4.2 SwiftPM、宏与调试体验:工具链进步让"变"更有底气

Swift 生态能持续变化,另一个不可忽视的支撑是工具链本身的进步。SwiftPM 从早期只能做包管理,慢慢变成了支持多平台构建、支持资源文件、支持宏编译的一体化工具。最典型的是宏(Macro)这个特性的引入,它允许你在编译期生成代码,像 @Observable 这种能力就是建立在宏基础上的。宏的价值在于:把一些之前运行时才发生的动态行为,提前到编译期去做,生成代码变得更可预测、更好调试。

我自己用过几个第三方宏之后,最大的感受就是"能省则省,但不能滥用"。宏可以隐藏很多样板代码,但也可能让代码变得难追踪,调试时你会看到展开后的代码,但源文件里只是一行 @SomeMacro。所以在项目里引入宏之前,我会确认这个宏是否开源、社区维护是否活跃、展开后的代码是否可控。这些判断标准跟当年选择第三方库一样,属于稳定的决策框架。

调试体验这些年也在肉眼可见地变好。并发调试现在能看到任务树,Data Race 检测工具也比以前直观,崩溃日志符号化越来越自动化。工具链做好了一件事:"让开发者把精力花在业务逻辑上,而不是环境配置上。"这个方向上的变化,属于默默发光的部分,不容易被看到,但收益是实实在在的。

5. 干草堆:Swift 十年不变的那些设计内核

5.1 值语义、内存安全、协议导向:为什么这些才是语言的根

聊完变化的部分,我想回到最核心的问题:Swift 那些不变的东西,到底是什么?为什么我认为它们比新特性更重要?

第一是值语义。Swift 里 struct 是默认推荐的数据类型,拷贝发生时是值拷贝,每个实例独立。这意味着数据在传递过程中不会因为某处修改而影响其他地方,大大减少了"隐式共享状态"带来的 bug。在并发环境里,值语义本身就是一种安全:你把一个 struct 传给一个 actor,actor 拿到的是独立副本,两边互不干扰。这一点从 Swift 1.0 到 6.x 始终坚持,是语言最底层的地基。

第二是内存安全。Swift 通过 ARC 管理引用计数,同时用编译期检查来避免悬垂指针和数据竞争。可选值(Optional)强制你处理"没有值"的情况,从语法层面避免了空指针崩溃。这些设计让 Swift 在保持高性能的同时,依然能给你一种"编译器在替我盯着"的安全感。

第三是协议导向编程。Swift 的协议和协议扩展让抽象变得灵活,你可以为任意类型增加行为,而不是非得套一层继承关系。这个特性让 Swift 既能写高性能的底层代码,又能构建出干净的领域抽象。十年里 API 层面的变化没有动摇这些根基。

所以我给新人的建议是:追新特性可以,但先花时间理解这几个稳定内核,它们才是你写任何 Swift 代码都通用的底层能力。学 @Observable 之前,先搞懂单向数据流;用 actor 之前,先理解隔离;写泛型之前,先琢磨协议。内核稳定了,外面的光影怎么变,你都不慌。

5.2 记录者的执念:第 121 期周报,我为什么还在写

写周报这件事,看似是"输出",对我来说其实是一种"输入"。每周为了写一期周报,我需要浏览大量的提案、博客、更新说明,筛选出真正有价值的信息,再用自己的话整理出来。这个过程强迫我把不熟悉的知识搞懂,而不是流于转发收藏。

到了第 121 期,我越来越相信一个朴素的道理:技术内容最重要的不是新,而是"结构"。一条新 API 的热度可能两周就过去,但一个帮你理解 API 背后设计思路的框架,可以长期伴你成长。这也是我在周报里尽量多写"为什么",而不是只写"是什么"的原因。

对想持续成长的开发者,我的建议很简单:找到一个你愿意长期输出的主题,把它当成你的"干草堆"。可以是对某个框架的源码解读,可以是系列化的踩坑总结,甚至可以是每周的学习笔记。不管外部怎么变,这个稳定的输出节奏会让你保持沉静,也会在某个时刻变成别人认识你的方式。第 121 期写到这里,我又一次确认了这个选择的价值。

6. 周报读者高频问题速查:并发、SwiftUI 与学习节奏

6.1 并发改造中最容易被卡住的 3 个问题

最近评论区、私信里问到最多的并发问题,集中在三个地方。

第一个是 actor 方法重入导致的"看似死锁"。注意,actor 的隔离不是绝对的串行阻塞,调用 actor 方法时,如果方法内部 await 了一个不会立即完成的异步操作,actor 是允许其他任务进来执行其他方法的,这就是所谓的"重入"。很多人第一次遇到时以为死锁了,其实只是执行顺序和直觉不一样。排查方法是:仔细看 actor 方法内部 await 前后的状态变化,别假设它整个过程独占 actor。

第二个是 Sendable 报错泛滥。从 Swift 5 切到 Swift 6 语言模式时,最常见的一百种报错里至少有八十种是关于 Sendable 的。我的处理顺序是:先处理"值类型跨隔离域传递"的场景,把这些类型改成 struct 或加 let 约束;再处理"单例/全局状态"的场景,给它们增加 @MainActor 或放入 actor;最后处理"闭包捕获可变状态"的场景,用 [weak self] 或显式标记 @Sendable。按这个顺序走,报错量会明显下降。

第三个是 async 桥接旧回调时的坑。用 withCheckedThrowingContinuation 时,如果旧的回调可能在多个时机被调用,或者可能永远不调用,你的 continuation 就会出问题。标准做法是:确保一个回调只被调用一次,并且设置超时保护。不少崩溃都藏在这种"回调不保证调用一次"的老接口里。

6.2 SwiftUI 状态问题排查速查表

我把最近项目里遇到的、以及周报读者反馈的典型 SwiftUI 状态问题整理成一张速查表,遇到问题先对号入座:

现象可能原因排查与解决思路
预览不刷新或崩溃Preview 缺少环境依赖或数据对象检查 .environment 注入,确认 @Observable 用 .environment(_:model) 传递
列表删除后 UI 卡顿或错乱数据模型缺少稳定唯一标识给 List 提供稳定 id,推荐用 Identifiable 协议,避免用数组下标做 id
导航栈状态重置NavigationStack path 与视图生命周期不一致确认 path 由 @State 或等价状态持有,避免放在全局单例里
修改数据但视图不更新观察链断裂,子视图读取数据时未形成依赖把数据访问移到需要刷新的视图层级,或用 @Bindable 建立绑定
旧组件混用出现运行时警告@Observable 对象被包装成 ObservableObject统一迁移到新观察宏,消除兼容层
异步更新 UI 崩溃在非主线程修改了视图状态给 ViewModel 标注 @MainActor,或把 UI 更新包进 MainActor.run

这张表不能覆盖所有情况,但覆盖了大概八成我见过的"SwiftUI 状态灵异事件"。记住一个底层原则:状态出了问题,先找"数据的源头在哪、谁持有它的生命周期",大多数问题都出在这两层没有对齐。

6.3 新特性学不过来?我的筛选原则

每年 WWDC 之后都会被新特性轰炸一轮,总有读者问:"那么多东西,到底该学什么?"我的筛选原则大概是这样的。

先看它解决什么问题。一个特性如果真的解决了某个长期困扰你的痛点,它才值得你花整块时间研究。比如严格并发检查,如果你总为数据竞争头疼,那它就是命中要害的特性;如果一个新组件只是换了个皮肤,那收藏一下看个热闹就行。

再看它是否改变你的"姿势"。一个特性如果是新瓶装旧酒,学习成本低,设计惯性还在;如果它改变了你的编码方式——像 SwiftUI 刚出来那样、像宏刚出来那样——那你需要认真跟进,因为它会直接影响未来的项目架构。

最后用 mini demo 验证,而不是收藏即学会。我每研究一个新特性,都会建一个最小的测试工程,跑通之后再写几十行笔记,最后挑有价值的部分写进周报。这套流程看起来慢,但长期积累下来,知识是成体系的,比三天打鱼两天晒网地刷教程有效得多。

最近重看莫奈晚年的干草堆系列,我注意到他越画越不像"草堆",而是逐渐变成了光影本身的形状。这个变化挺有意思——当你对一个事物足够熟悉之后,你看到的就不再是它的表面,而是它和周围环境互相作用的那些关系。我对 Swift 大概也有点类似的感觉:新框架也好,新语法也好,看多了之后,我更关心的反而是它们和原有设计哲学之间的呼应。技术的风往哪吹,我们控制不了,但自己手里的那个干草堆——不管是多年练出的代码手感,还是持续输出的习惯——是能守住的。希望这一期周报,能让你在追赶变化的同时,也别忘了回头看看自己已经打牢的那些东西。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询