
老司机 iOS 周报,只为你呈现有价值的信息。
你也可以为这个项目出一份力,如果发现有价值的信息、文章、工具等可以到 Issues 里提给我们,我们会尽快处理。记得写上推荐的理由哦。有建议和意见也欢迎到 Issues 提出。
@Crazy:Android MessageQueue 是从 Android 的核心框架,从 API1 就已经存在了,这次 Android 针对它进行了重构,是非常大的优化。
原始 MessageQueue 存在的问题: Android 的 MessageQueue 在过去的二十多年里靠着一把 monitor 同步锁保护,虽然没有大的问题,但是在多核多优先级场景下,这把锁会引发多线程争用同一把锁,并且进一步引发高优先级 UI 线程被低优先级后台线程间接拖慢。
新 MessageQueue 设计核心 DeliQueue: 新的 DeliQueue 采用 lock-free 数据结构的设计方式来解决上面的问题,简单用一句话来描述就是 “可以无锁写入的多生产者线程与独占排序和结构整理能力的单消费者 Looper 线程。” 核心方式就是利用原子操作来替代锁,下面我们把 lock-free 拆解一下,不涉及很多的源码。
long seq = when != 0 ? ((long) sNextInsertSeq.getAndAdd(this, 1L) + 1L) : ((long) sNextFrontInsertSeq.getAndAdd(this, -1L) - 1L);if (WaitState.isCounter(waitState)) {// 情况 A:looper 已醒} elseif (msg.when >= WaitState.getTSMillis(waitState)) {// 情况 B:新消息不比当前 deadline 更早,我们不需要唤醒} elseif (msg.isAsynchronous()) {// 情况 C:新消息更早,且是 async(绕过消息屏障),需要唤醒} else {// 情况 D:我们需要看消息屏障状态,决定是否需要唤醒if (blockedByBarrier) { newWaitState = WaitState.incrementDeadline(waitState); checkBarrier = barrier; needWake = false; } else { newWaitState = WaitState.initCounter(); checkBarrier = null; needWake = true; }}privatestaticfinallong MPTR_TEARDOWN_MASK = 1L << 63;// 生产者增引用(incrementMptrRefs)while (true) {finallong oldVal = mMptrRefCountValue;if ((oldVal & MPTR_TEARDOWN_MASK) != 0) {returnfalse; // 已 teardown,拒绝新引用 }if (sMptrRefCount.compareAndSet(this, oldVal, oldVal + 1L)) {returntrue; }}// 生产者减引用(decrementMptrRefs)long oldVal = (long) sMptrRefCount.getAndAdd(this, -1L);if (oldVal - 1 == MPTR_TEARDOWN_MASK) { LockSupport.unpark(mLooperThread); // 我是最后一个活引用,且 looper 在等}// 与 wake 的协作privatevoidconcurrentWake(){if (incrementMptrRefs()) {try { nativeWake(mPtr); }finally { decrementMptrRefs(); } }}// 代码位置 MessageStack,所有线程都可以调用publicintmoveMatchingToFreelist(Message.MessageCompare compare, Handler h, int what, Object object, Runnable r, long when){ Message current = (Message) sTop.getAcquire(this); Message prev = null; Message firstRemoved = null;int numRemoved = 0;while (current != null) {if (messageMatches(current, compare, h, what, object, r, when) && current.markRemoved()) {if (firstRemoved == null) { firstRemoved = current; } current.clearReferenceFields();// nextFree links each to-be-removed message to the one processed before. current.nextFree = prev; prev = current; numRemoved++; } current = current.next; }if (firstRemoved != null) { Message freelist;do { freelist = mFreelistHeadValue; firstRemoved.nextFree = freelist;// prev points to the last to-be-removed message that was processed. } while (!sFreelistHead.compareAndSet(this, freelist, prev)); }return numRemoved;}// looper 准备派发的消息已被并发删除if (found != null && !peek) {if (!found.markRemoved()) {continue; // 别人已经把它标记为删除,重新找下一条 } mStack.remove(found);}// looper 线程MessageStack.pop(looper 线程):if (!m.markRemoved()) {returnnull; // 别人已经标记了,我让出}最后我们总结一下新的设计的整体流程: 拿插入序号(原子 getAndAdd,1 条 CPU 指令) -> 写消息字段(不用同步控制) -> mStack.pushMessage(Treiber stack CAS push。失败重试,平均 1–2 次 CAS) -> 唤醒决策循环(读 mWaitState,CAS 写新状态) -> 可能调 concurrentWake。完成整体 pushMessage 操作,全程没有 synchronized 关键字,最坏情况也只有几次 CAS retry,最快路径 0 次内核调用,大大减轻了系统负担。
整篇文章其实不止写了 lock-free 数据结构的设计,其余还有很多,比如 Treiber stack、比如如何利用双链表机制是让 Looper 在线程内高效地把某个节点从 stack 链中摘掉。还有 Google 如何利用 Perfetto 和 PerfettoSQL 进行大量的 trace 分析,确认问题以及修复问题后的验证。可以说这篇文章中的每一部分都可以拿出来单独写一篇比较好的操作指南针,也可以看出 Google 在针对 MessageQueue 的修改上是有多么的慎重,以及在这种多线程上的恐怖控制力,可以说这是一篇值得所有人反复阅读的文章。
@ChengzhiHuang: sqlite 是常见的端用存储,一般也都会辅以开启 Write-Ahead Logging(WAL) 模式提升性能。对于一些低存储用户,我们还会辅以开启 incremental_vacuum 定期整理 .-wal 文件进一步减少磁盘占用(注:直接使用 vacuum 是不被推荐[3]的,但是如果数据库本身已经存在,则必须先执行一次完整的 vacuum 才能开启 incremental_vacuum,因此最好是新建的时候默认打开)。本文对 incremental_vacuum 进行了进一步的细分,研究了配置不同的阈值(每次清理的页数)下,整体数据库的表现。大家可以参考自己数据库的实际情况选择不同阈值分批 incremental_vacuum 。
同时提醒大家记得在 incremental_vacuum 完成后再手动进行 checkpoint 才能有效减少磁盘占用,不然只是缩小了 free pages 的数量。
@阿权:本文作者在开启 Swift InferSendableFromCaptures(SE-0418)特性后,遇到 SwiftUI 导航修饰器传递视图构造器函数引用的 Actor 隔离警告的问题。根本原因是:警告只是 Swift 5 迁移模式下的一个产物,升级到 Swift 6 后并不是问题。怎么理解呢?
InferSendableFromCaptures)来提前测试 Swift 6 的行为时,它可能会暴露一些问题,但由于底层的检查模型仍是旧版的,所以会产生一些在最终模型中并不存在的“过渡性警告”。如何去一步步找到问题的根因也是文章的重点,通过作者的探索也能给到我们一些开发实践的建议:
-enable-upcoming-feature 等标志在旧语言模式下测试新特性时,要意识到看到的警告可能带有“过渡性”特征,需要结合最终的语言模型来理解其真正含义。@Barney:这篇文章系统梳理了 Swift 里 lazy 属性的行为边界,重点不是语法本身,而是它在 SwiftUI 里的常见误用。作者先回顾了 lazy 适合解决的几类问题:延迟昂贵初始化、缓存只需计算一次的结果,以及依赖 self 的初始化;随后指出一个很容易踩的坑:SwiftUI 的 View 是值类型且会频繁重建,而 lazy 首次访问时需要发生写入,这使它既不适合作为稳定缓存,也无法直接放进 body 所依赖的视图属性里。文章给出的实践建议也很明确:在 SwiftUI 中优先用 @State、@StateObject 或对象持有者管理生命周期,把 lazy 留给 class、service、formatter 或计算代价较高的缓存对象。对经常在 SwiftUI 中做性能优化的同学很有参考价值。
@DylanYang:作者基于 SwiftUI 的 PreferenceKey 与锚点系统,实现了一款可复用的引导组件,无需依赖 UIKit 即可完成视图高亮、圆角镂空遮罩、自适应提示卡片展示与多步骤平滑动画切换。该组件适配导航栈、滚动视图、安全区、弹窗等各类场景,通过 tutorialSpotlight modifier 和 tutorialSpotlightSource modifier 即可快速接入,还支持自定义高亮内边距、圆角、背景点击关闭等配置,能便捷搭建完整的界面引导流程。感兴趣的开发同学可以阅读下具体的实现过程。
@david-clang:SwiftPM 长期受限于 Git 全量克隆导致的解析缓慢与磁盘占用过大。受此影响的 Ordo One 公司提交了优化提案 (PR #9870)[8],通过引入源码归档下载路径实现大幅优化。该方案能保持 Public API 不变,且无需开发者修改 Package.swift 。其核心流程如下:
git ls-remote --tags —— 发现可用版本(无新增 API,与现有机制一致)。Package.swift —— 检查工具版本(Tools Version)的兼容性。降级机制 :
git clone --mirror)。基准测试与性能收益:
swift-composable-architecture (TCA)、SwiftLint 等不同规模(9 至 67 个依赖项)的知名开源项目。分别对比新旧方案在冷解析(清空 .build 与全局缓存,模拟 CI 环境)和热解析(保留全局共享缓存,模拟本地开发)下的耗时与磁盘增量。.build/ 目录的磁盘占用平均锐减 3 倍(例如,某重度依赖项目的体积由 1.8GB 缩减至约 600MB)。重新开始更新「iOS 靠谱内推专题」,整理了最近明确在招人的岗位,供大家参考
具体信息请移步:https://www.yuque.com/iosalliance/article/bhutav 进行查看(如有招聘需求请联系 iTDriverr)
我们是「老司机技术周报」,一个持续追求精品 iOS 内容的技术公众号,欢迎关注。
关注有礼,关注【老司机技术周报】,回复「2024」,领取 2024 及往年内参
同时也支持了 RSS 订阅:https://github.com/SwiftOldDriver/iOS-Weekly/releases.atom 。
🚧 表示需某工具,🌟 表示编辑推荐
预计阅读时间:🐎 很快就能读完(1 - 10 mins);🐕 中等 (10 - 20 mins);🐢 慢(20+ mins)
Under the hood: Android 17’s lock-free MessageQueue: https://android-developers.googleblog.com/2026/02/under-hood-android-17s-lock-free.html
[2]SQLite: Vacuuming the WALs: https://www.theunterminatedstring.com/sqlite-vacuuming/
[3]不被推荐: https://developer.apple.com/fr/videos/play/wwdc2019/419/?time=1266
[4]A Small SwiftUI Warning and a Long Journey to Understand It: https://alejandromp.com/development/blog/a-small-swiftui-warning-and-a-long-journey-to-understand-it/
[5]Lazy Properties in Swift - Why They Don't Always Work in SwiftUI: https://www.sagarunagar.com/blog/lazy-properties-swiftui-pitfalls/
[6]A Reusable Spotlight Onboarding Component in SwiftUI: https://livsycode.com/swiftui/a-reusable-spotlight-onboarding-component-in-swiftui/
[7]SwiftPM: 2x faster resolves, 3x smaller disk footprint: https://forums.swift.org/t/swiftpm-2x-faster-resolves-3x-smaller-disk-footprint/85750
[8]优化提案 (PR #9870): https://github.com/swiftlang/swift-package-manager/pull/9870