普通视图
Neo-Cupertino 档案:撕开 Actor 的伪装,回归 Non-Sendable 的暴力美学
赛博深渊(上):用 Apple Foundation Models 提炼“禁忌知识”的求生指南
![]()
🍎 引子
新九龙城的雨从未停过。霓虹灯的废气在湿漉漉的街道上晕染开来,像极了那个死于代码过载的倒霉蛋老王流出的脑浆。
在贫民窟第 404 区的一间昏暗安全屋里,一名代号为“老 K”的黑客正对着一块发着幽蓝光芒的屏幕,手指在键盘上敲击出残影。墙角的服务器风扇发出濒死的哀鸣,仿佛随时都会起飞爆炸。
![]()
在这个被巨型企业“果核公司(Fruit Corp)”统治的赛博世界里,数据就是生命,而算力就是货币。
老 K 刚刚从公司的主机里偷出了一份代号为“创世纪”的加密文本,文件大得惊人,如果不赶紧在本地进行 “摘要提炼”,追踪程序(那些被称为“猎犬”的 AI)顺着网线摸过来,把他那可怜的脑机接口烧成灰烬只是时间问题。
在本次冒险中,您将学到如下内容:
- 🍎 引子
- 🧠 缩水的“大脑”与本地化的艺术
- 🛠️ 装备检查:不要试图在烤面包机上运行核程序
- 🕵️♂️ 侦测可用性:敌我识别系统
- 💉 注入代码:瞒天过海
- 🎭 模拟测试:在母体中演练
- 🎆 尾声:黎明前的微光
但他不能上传到云端处理。云端是“它们”的地盘。他必须在本地,用那个刚刚解禁的传说级武器——Apple Foundation Models。
🧠 缩水的“大脑”与本地化的艺术
“听着,菜鸟,”老 K 转过头,仿佛打破了第四面墙,对着作为学徒的你说道,“想要活命,就得学会如何在端侧(On-device)跑大模型。”
Apple Foundation Models 提供了一种能在用户终端设备(不管是你的义体植入终端,还是手里的 iPhone/Mac)上本地运行的大语言模型(LLM)。
![]()
你要知道,传统的 LLM 就像是住在数据中心里的巨型怪兽,吃的是高功率 GPU,喝的是海量的显存和电力。想要把这种怪兽塞进你的口袋里,不仅需要勇气,还需要黑科技。
这里有两个关键的 “瘦身” 魔法:
- 参数削减 (Reducing Parameters):把脑子里那些没用的神经元切掉,只保留核心逻辑。
- 模型量化 (Quantizing Model Values):如果说原来每个神经元需要 32 位浮点数的高精度,现在我们把它压缩到 4 位或 8 位。虽然精度略有下降,但体积小到可以忽略不计。
![]()
⚠️ 风险提示:这些被“阉割”过的模型依然保留了 LLM 的通病——幻觉 (Hallucinations)。它可能会一本正经地胡说八道,就像喝了假酒的算命先生。但在提炼摘要、理解文本、甚至简单的数据生成任务上,它依然是把好手。
好了,废话少说,让我们开始改装你的代码,让它能驾驭这股力量。
🛠️ 装备检查:不要试图在烤面包机上运行核程序
想要驾驭 Apple Intelligence,你的硬件得跟得上。
“别拿你那台老古董来丢人现眼。”老 K 吐了一口烟圈。
硬性指标:
- 操作系统:必须是 macOS 26.0 或更高版本(对于 iOS 设备,则是 iOS 26/iPadOS 26)。
- 硬件支持:设备必须原生支持 Apple Intelligence(那种山寨的义体插件不仅没用,还会炸)。
- 开关状态:用户必须在设置里手动开启了 Apple Intelligence。
![]()
☠️ 避坑指南:如果你在 macOS 26 的虚拟机里折腾,大概率会翻车。Apple Foundation Models 对虚拟化环境过敏。找台真机,或者支持该特性的物理设备。
![]()
🕵️♂️ 侦测可用性:敌我识别系统
我们的目标是那个 Share Extension(分享扩展)。我们要让它变成一个能吞噬长文本并吐出精华摘要的黑洞。
![]()
现在的 App 像个傻子,只会把你选中的文字原样复读一遍。我们要改造它。
首先,在 SummarizeExtension 文件夹下创建一个新的 SwiftUI 视图,命名为 ModelCheckView.swift。这就像是我们的看门狗,用来检测当前环境是否安全。
![]()
在该文件顶部引入这一行禁忌的咒语:
import FoundationModels // 引入 Apple 的本地模型库
然后在结构体里加入这些属性:
// 这里的 sharedText 是我们要处理的“赃物”(文本)
let sharedText: String
// 任务完成后的回调,毕竟我们得知道什么时候跑路
let onDone: () -> Void
// 核心:获取系统默认的语言模型实例
let model = SystemLanguageModel.default
接下来,把原本的 body 替换成这一段充满求生欲的代码:
// 1. 检查模型的可用性状态
switch model.availability {
// 2. 状态:可用。谢天谢地,我们可以干活了
case .available:
// 这里调用真正干活的摘要视图(后面我们会讲)
SummaryView(sharedText: sharedText, onDone: onDone)
// 3. 各种不可用的“死法”
case .unavailable(.deviceNotEligible):
Text("⚠️ 你的设备太老了,跑不动 Apple Intelligence。")
case .unavailable(.appleIntelligenceNotEnabled):
Text("🛑 Apple Intelligence 虽已就绪,但你还没打开开关。快去设置!")
case .unavailable(.modelNotReady):
Text("⏳ 模型还没热身完毕,稍后再试。")
// 注意:在模拟器里看到这个,通常意味着你的宿主机不支持,或者你在虚拟机里套娃。
// 4. 未知错误,最可怕的一种
case .unavailable:
Text("👾 未知错误阻止了 Apple Intelligence 的运行。可能是赛博幽灵作祟。")
}
![]()
老 K 的技术旁白:
-
model.availability:这是你的盖革计数器。一定要先 switch 它,不然直接调用模型会导致程序崩溃,就像在没有氧气的地方点火一样。 -
unavailable的各种姿势:一定要给用户(或者你自己)清晰的反馈。与其让 App 闪退,不如告诉他“你的装备不行”。
![]()
💉 注入代码:瞒天过海
现在,我们要把这个检测机制植入到 ShareViewController.swift 里。这个文件是连接古老的 UIKit 世界和新锐 SwiftUI 世界的桥梁。
找到 showSwiftUIView(with:) 方法,我们要玩一招“偷梁换柱”。
// 用我们新的 ModelCheckView 替换掉原来的直接调用
let wvc = UIHostingController(
rootView: ModelCheckView(
sharedText: text,
onDone: closeExtension // 事情办完就销毁现场
)
)
这样,当你在 Safari 里选中一段长得令人发指的文本,点击“分享 -> LocalSummarizer”时,系统会先经过我们的 ModelCheckView 查岗。
![]()
🎭 模拟测试:在母体中演练
“别急着上线,现在的网络全是眼线。”老 K 按住你的手,“先在 Xcode 的模拟环境中跑一遍。”
即使你的设备牛逼哄哄,你也得测试一下如果用户没开功能会怎样。
![]()
- 在 Xcode 顶部选择
SummarizeExtension方案。 - 点击 Edit Scheme...。
- 在 Run -> Options 标签页下,找到 Simulated Foundation Models Availability。
![]()
这就是你的上帝模式开关。你可以把它设为:
- Apple Intelligence Not Enabled:看看你的 App 会不会乖乖提示用户去开启。
- Off:这是默认值,反映你真实设备的状态。
![]()
⚠️ 警告:测完记得把它改回 Off。别到时候明明设备支持,却因为这里没改回来而对着屏幕怀疑人生,那种感觉就像是找了半天眼镜结果眼镜架在鼻子上一样蠢。
![]()
为了让主 App (LocalSummarizer) 也能自检,我们顺手改一下 ContentView.swift:
import FoundationModels
// ... 在 body 里 ...
switch SystemLanguageModel.default.availability {
case .available:
Text("✅ 此设备已准备好接入 Apple Foundation Models。")
// ... 其他 case 照抄上面的 ...
}
![]()
🎆 尾声:黎明前的微光
老 K 敲下最后一个回车键。屏幕闪烁了一下,那段数百万字的冗长文本,瞬间被 Apple Foundation Models 压缩成了寥寥几百字的精华摘要。
没有联网,没有上传,一切都在悄无声息中完成。
![]()
“搞定了。”老 K 拔掉数据线,嘴角露出一丝不易察觉的微笑,“猎犬们还在云端嗅探我们的踪迹,殊不知我们已经带着情报,大摇大摆地从它们眼皮子底下溜走了。”
![]()
窗外的雨似乎小了一些。在这个被数据淹没的时代,掌握本地大模型技术,不仅仅是为了开发几个花哨的功能,更是为了在这个监视无处不在的赛博丛林里,保留最后一份隐私和自由。
![]()
你看着手里显示着 "Summary Generated" 的屏幕,深知这只是开始。下一章,我们将利用这股力量,去触碰更深层的禁忌……
(未完待续...)
理财学习笔记(2):不懂不投
这是本系列的第 2 篇,主题是:不懂不投。
我们刚开始投资理财的时候,通常会寻求以下这些方法来找到投资标的。
常见的错误办法
1、问朋友。我们通常会问那些看起来投资理财收益比较高的朋友,问他们应该买什么股票。
对于朋友推荐的股票,我们通常会“无脑”买入。但如果有一天,股票突然大幅回撤,我们通常就会陷入恐慌。我们会怀疑:这个朋友到底靠不靠谱?他之前赚钱是靠运气,还是因为现在判断出了问题?接着,我们就会陷入各种猜忌、焦虑和紧张中,最后甚至睡不着觉。如果股票持续下跌,我们甚至可能割肉离场。所以说,跟着朋友买其实并不那么靠谱。
2、看走势。我们可能会去看某些股票或基金的历史走势。看到它在过去三年或五年涨得很好,我们就买入。这也是理财 App 或者某些理财经理推荐的首选理由:它过去 X 年涨幅 XX,排名 XX。
但这很容易陷入“价值陷阱”,比如:
周期性误判:有些股票仅仅是在某个周期内表现优秀。比如房地产在过去十年涨得很好,但这并非因为单体公司有多好,而是因为当时整个大环境让所有房企都很赚钱。如果你仅仅因为过去业绩好而买入,一旦遭遇经济下滑或泡沫破裂,就会面临巨大的损失。
均值回归陷阱:很多股票或基金某年表现出色,仅仅是因为那一年的风格与它匹配。所有行业都有“大小年”之分,未来遇到“小年”时,表现自然就会变差。我把这叫做“均值回归”。
这就好比考试:你的平均水平可能是第三名。发挥好的时候能考第一名,发挥不好则可能掉到第五名,但你始终是在第三名上下徘徊。
很多基金经理或股票的表现也是在自身价值上下震荡。如果你在高点买入,在回撤时就会损失惨重,甚至被深套。
3、跟风。跟风是 A 股散户的常见操作,某个时间什么热,就跟风买什么,涨了就快速卖掉,主打一个击鼓传花,赌谁是最后接盘的大傻子。
这种情况下,我们假设你的胜率是 50%。每次获胜挣 20%,每次赌失败亏 20%。如果你进行了 10 次这样的操作,那你整体的收益期望就是 (1.2^5)*(0.8^5)=0.82,所以你折腾了半天,最后 1 块钱的本金变成了 0.82 元。
当然,如果有人认为自己跟风总是赢,这也是有可能的,但是因为自己不敢长期持有,只要涨一点点就卖,其实每次挣的是一点点收益。但是如果偶尔遇到亏损的时候,自己舍不得卖掉,就会一次亏很多。做这种短线操作的人,需要极强的止损纪律,大部分人也是很难做到的。
不懂不投
所以回到股票投资,我觉得投资理财一定要自己懂才行。如果你完全不懂或一知半解,这些都会成为你的陷阱。因为:
- 心理层面:不懂的人往往“拿不住”。当股票大幅下跌时,无论是否割肉,你都会极度焦虑、睡不好觉,担心本金损失。
- 投资层面:如果你懂,面对下跌说不定还能逆势加仓;即便不加仓,至少能睡个好觉。
此外,世界上还有很多投资陷阱。有些人甚至专门为“制造陷阱”而生,比如搞资金盘、割韭菜或传销。这些行为有些是非法的,有些则游走在法律边缘。如果大家没有能力分辨这些陷阱,很容易就在投资理财中遭遇严重的亏损。
小结
小结一下,常见的错误投资:
- 问朋友。其实本质上信的是朋友的业绩,朋友如果业绩下滑,就会怀疑。
- 看走势。其实本质上是用过去业绩替代未来判断,不靠谱。
- 跟风。纯投机,50% 胜率下期望是负的。
心理层面,只有懂了,才可能拿得住,睡得着觉。
另外,真正懂也可以避免很多骗局。
以上。
NSProcessInfoThermalState 的作用
3-27.【函数式编程】讨论 Combine 的 Publisher 如何体现 Functor / Monad 特性。
我的笔记系统
笔记大概分为三类:个人相关、工作相关和知识相关。个人向的主体是「我」,通常只对自己有意义;工作向的笔记自然与工作相关;知识向的笔记则致力于形成知识网络,时效性较长,也是本文讨论的重点。
相信大家都有用过大语言模型(LLM),如 ChatGPT,DeepSeek,豆包,Gemini 等等,给一个问题,就能得到不错的答案,那么在大语言模型不断进化,AI 工具愈发强大的当下,是否还有记笔记的必要?我认为:不仅有必要,而且比以往更重要。 但前提是,我们需要重新定义「记笔记」这件事。
笔记是什么?我把它看作 「外化的思考脚手架」。我们的大脑工作内存有限,只能同时处理 3-5 个想法,笔记可以将大脑从「记忆」的负担中解放出来,全力投入到「运算」中。笔记不是最终的目的,而是用于构建更高层建筑的工具,比如写文章,做决策,解决问题,它的价值在于它能支撑你爬得更高。
更形象的比喻或许是:预处理过的「半成品料理包」。当你来到厨房(需要解决问题/写作/决策)时,不需要从洗菜、切菜开始,而是直接拿出切好的配菜、调好的酱汁,就能快速烹饪出一道大餐。
在 AI 时代,有什么不懂直接问 AI 就好了,为什么还要记笔记?因为缺少内化的知识网络,就问不出好问题,没有好问题,就很难得到好答案,就无法最大程度地挖掘 AI 的潜力。大语言模型遵循的是 GIGO(Garbage In Garbage Out)原则,没有好的输入,就很难得到好的输出。笔记系统可以帮助我们构建/强化知识网络,从而问出好问题。
比如前一阵很火的 Dan Koe 的 How to fix your entire life in 1 day 这篇文章,看完之后,可能觉得很有道理,但不一定能问出合适的 follow up,比如文章提到的思想跟斯多葛的消极想象有什么联系?或文章提到的身份认同理论是否与 Atomic Habits 中提到的身份认同概念一致?以这些问题为切入点,可能又能获得到一些不错的新的知识点和看世界的角度,进而丰富自己的知识体系。
工作流概览
一个好的笔记系统不仅仅是工具的堆砌,更是信息的流动。我的工作流包含五个阶段:
- 捕获:极低阻力地快速收集。
- 存储:将待处理内容归位到合适的介质。
- 处理:提炼、消化原始内容。
- 回顾:建立连接,内化知识。
- 产出:用笔记解决实际问题,形成闭环。
一、捕获阶段 (Capture)
核心原则:极低阻力。灵感和信息稍纵即逝。这个阶段唯一的任务就是把脑子里的想法或外界的信息扔进一个固定的盒子里。此刻不要整理,也不要分类,只要丢进去即可。
我推荐 Apple Notes 的 Quick Note,系统级集成,很方便。Mac 上一键唤出,iPhone Control Center 随时点击。支持富媒体(语音、手绘、链接),就像一张随手可得的便利贴。
我的信息主要来自 Twitter(X)、YouTube、Newsletter、博客以及与 Gemini 的对话。为了解决「想看视频但没时间/效率低」的问题,我还构建了一套自动化流程:用 js 脚本调用 YouTube API 抓取字幕,通过 LLM 进行精简并整理成文章,最后打包成 Epub 电子书。这让我能像阅读文章一样「阅读」视频,大大提升了效率。

这里要避免沉迷于「寻找好内容」这种多巴胺陷阱,建议设定特定的「进货时间」(如周末早晨),批量获取信息,然后断连。同时不要试图在捕获阶段去消化内容,那样会打断「狩猎」节奏。
二、存储阶段 (Storage)
捕获的内容通常是链接、书籍或长文。这个阶段的目标是让它们「各归其位」,等待处理。
「链接」我推荐 Goodlinks。它没有订阅制,设计优雅,功能纯粹。我把它当作我的链接「中转站」。
「电子书」我没有使用 Apple Books 或 Calibre,而是直接使用 macOS Finder + Tags。把待看的书扔进文件夹,看完的书打上特定的标签,这样只要 filter by tag,就能看到看过的书和没看的书。这么做的一个原因是不争气的 Apple Books,它不支持 Smart Filter,只能手动创建 Collection,这样就很不方便筛选出没有看的书,我希望它像一个 Queue 或 Stack,随着书一本一本被看完,这个列表里的内容也会逐渐减少。还有一个原因是,书放进去后,再导出来也不太方便。
三、处理阶段 (Processing)
这是整个工作流中最重要,也是最容易被忽视的部分,很多人的笔记系统往往停在了上一步。这一步的目的是蒸馏(Distillation),提炼出有价值的内容,而不是简单地复制粘贴。
这个阶段最重要的,也是最难的部分,是要为它留出时间(比如每天晚上),因为做这件事可能没有那么愉悦,如果不专门留时间,几乎肯定会被其他阻力更小的事情代替。
这个阶段我用到的工具是 Dia 浏览器,没有直接在仓库中处理是不想看着一大堆未处理的内容产生焦虑,选择 Dia 浏览器是因为它的 Vertical Sidebar 和 Split view 很方便,同时因为它是浏览器,对链接天然友好,还能方便地唤出 Gemini。
浏览器可以打开 pdf,但默认不支持 epub,所以我又做了一个浏览器的 epub 插件,可以一边看书,一边与 Gemini 就书的内容进行交流。

待处理的内容通常比较长,或者是非母语的内容,为了提高效率,我会先让 Gemini 对内容进行压缩,如果感兴趣,再去看原文,然后与 Gemini 就里面的内容进行深度的交流。这是一个例子。交流完后,通常会有这些产出:
- 一篇原文的精简版(放到笔记 App 里)
- 一篇讨论后的笔记(放到笔记 App 里)
- 一些原文的精彩摘录(放到笔记 App 里)
- 方便录入到 Anki 的卡片(整理成实体卡片)
- 相关推荐
Anki 相关的 App 一直用不起来,还是更喜欢实体的卡片,所以会把相关的知识点写到卡片上,顺便加深下印象。

处理后的笔记,我选择存放在 Bear 中。
- 为什么不选 Obsidian? 它确实功能强大且免费,也有丰富的插件系统,但我用起来总觉得不够「舒服」。
- 为什么不选 Apple Notes?它对 Markdown 的支持不友好,内容也有点封闭,写作体验也不如 Bear。
选择 Bear 还有一个好处,它的笔记可以很方便地导出为 Markdown,方便二次加工和后续迁移。孤立的笔记是死的。让笔记活过来的关键是Link(链接)。因为 Bear 的笔记都存在本地的一个 SQLite 数据库里,所以可以很方便地读取和处理。我写了一个 js 脚本,将 Bear 里的笔记内容向量化(Vectorization),然后计算余弦相似度,自动生成「相关笔记」列表。
四、回顾阶段 (Review)
把笔记存进去如果不看,那意义也不大。为了方便回顾,我做了一个 Web App(notes.limboy.me),每次随机展示一篇笔记作为起点,然后通过「相关笔记」进行漫游。同时也会在碎片时间把上一个阶段生成的卡片拿出来翻一翻,加深印象。

五、产出阶段 (Output)
笔记不是目的,它是为了帮助生成洞见(Insight)、新的看事物的角度和强化知识网络而存在,最好的方式就是输出,比如写文章、做分享、做决策等。以写文章为例,如果想写一篇关于「习惯养成」的文章,不再是面对空白文档抓耳挠腮,只需在笔记库里搜索「习惯」、「行为心理学」,把相关的 5-6 个笔记块(料理包)调出来,重新排列组合,加上新的连接词,文章的 80% 就完成了。
结语
如果没有一套运行顺畅的笔记系统,没有为消化笔记专门留出时间,没有输出的压力,那么笔记的价值就会大打折扣,再好的工具也无法做到第二大脑。希望这篇文章能给你带来些帮助和启发,如果你有好的想法和经验,也欢迎分享。
2-6.【Concurrency】TaskGroup 的设计目的是什么?它如何保证并发安全?
一句话先给结论
TaskGroup的设计目的,是在保持结构化并发的前提下,
安全地表达“一组并发执行、统一收敛结果的子任务”。
或者更狠一点的说法:
TaskGroup = “不会失控的 fork–join 并发”。
1️⃣ 为什么需要 TaskGroup?(设计动机)
先看一个“天真实现”的问题:
var results: [Int] = []
Task {
Task { results.append(await f1()) }
Task { results.append(await f2()) }
Task { results.append(await f3()) }
}
❌ 问题一堆:
-
results有数据竞争 - 子任务生命周期不受控
- 取消无法统一传播
- 无法确定什么时候全部完成
TaskGroup 想解决的正是这些问题
let sum = await withTaskGroup(of: Int.self) { group in
group.addTask { await f1() }
group.addTask { await f2() }
group.addTask { await f3() }
var total = 0
for await value in group {
total += value
}
return total
}
2️⃣ TaskGroup 的核心设计目标
🎯 目标 1:结构化并发(Structured Concurrency)
-
所有子任务:
- 必须在 group 作用域内完成
- 不能逃逸
-
离开
withTaskGroup:- 要么所有完成
- 要么全部取消
👉 不会“偷偷活着的任务”
🎯 目标 2:安全地共享结果,而不是共享状态
TaskGroup 的哲学是:
并发任务之间不共享可变状态,只通过结果汇合
-
子任务:
- 不写共享变量
- 只
return值
-
父任务:
- 串行地消费结果
🎯 目标 3:明确的并发边界
-
并发只发生在:
group.addTask
-
收敛点是:
for await value in group
这让代码在语义上可推理。
3️⃣ TaskGroup 的工作模型(非常重要)
逻辑结构
Parent Task
└─ TaskGroup
├─ Child Task 1
├─ Child Task 2
└─ Child Task 3
特点:
-
子任务都是:
- 当前 task 的子 task
- 自动继承取消 / 优先级 / actor
生命周期规则(强约束)
| 行为 | 结果 |
|---|---|
| 父 task 被取消 | group 中所有子任务被取消 |
| 任一子任务抛错(throwing group) | 其余子任务全部取消 |
| 离开作用域 | 等待所有子任务完成 |
4️⃣ TaskGroup 如何保证并发安全?
这是你问题的核心 👇
✅ 1. 类型系统层面的隔离
withTaskGroup(of: Int.self) { group in
group.addTask {
// 只能返回 Int
}
}
- 子任务无法直接访问 group 的内部状态
- 只能通过 返回值通信
✅ 2. 串行消费结果
for await value in group {
// 这里是顺序执行的
}
- 即使子任务并发完成
- 结果的处理是单线程、顺序的
- 所以你可以安全地:
var sum = 0
sum += value
✅ 3. 作用域限制(无法逃逸)
你做不到:
var g: TaskGroup<Int>?
withTaskGroup(of: Int.self) { group in
g = group // ❌ 不允许
}
- group 不能被存储
- 不能被跨作用域使用
👉 这从根源上消灭了悬垂并发。
✅ 4. 自动取消传播
- 父取消 → 子取消
- 错误 → 全部取消
这避免了:
- 子任务写了一半共享状态
- 父任务已经不关心结果
✅ 5. 与 actor 隔离完美配合
actor Store {
func loadAll() async {
await withTaskGroup(of: Item.self) { group in
for id in ids {
group.addTask {
await fetchItem(id)
}
}
for await item in group {
self.items.append(item) // actor 内,安全
}
}
}
}
- 子任务不直接修改 actor 状态
- 所有 mutation 都在 actor 上顺序执行
5️⃣ TaskGroup vs async let(顺便对比)
| 特性 | TaskGroup | async let |
|---|---|---|
| 子任务数量 | 动态 | 静态 |
| 结果处理 | 流式 | 一次性 |
| 错误处理 | 可逐个 | 一起 |
| 适合场景 | 批量 / 不定量 | 少量固定 |
6️⃣ 常见误区(非常常见)
❌ 误区 1:TaskGroup 里共享变量是安全的
var sum = 0
withTaskGroup(of: Int.self) { group in
group.addTask { sum += 1 } // ❌ 数据竞争
}
子任务仍然是并发的。
❌ 误区 2:TaskGroup 会限制并发数量
不会。
如果你要限流,需要:
withTaskGroup { group in
// 自己控制 addTask 的节奏
}
或使用 semaphore / AsyncSemaphore。
7️⃣ 终极总结(可以直接背)
TaskGroup 的设计目的,是在结构化并发模型下,
安全地表达“多个并发子任务 + 统一收敛”的计算模式。
它通过作用域约束、结果传递而非状态共享、串行消费、自动取消传播,
从语言和类型系统层面避免了数据竞争和失控并发。