<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.6">Jekyll</generator><link href="https://mengtnt.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://mengtnt.com/" rel="alternate" type="text/html" /><updated>2026-04-16T15:05:22+00:00</updated><id>https://mengtnt.com/feed.xml</id><title type="html">mengtnt的Blog</title><subtitle>走自己的路，让别人说去吧</subtitle><author><name>mengtnt</name></author><entry><title type="html">普通人用小龙虾可以做什么</title><link href="https://mengtnt.com/2026/04/16/14-04.html" rel="alternate" type="text/html" title="普通人用小龙虾可以做什么" /><published>2026-04-16T06:57:51+00:00</published><updated>2026-04-16T06:57:51+00:00</updated><id>https://mengtnt.com/2026/04/16/14-04</id><content type="html" xml:base="https://mengtnt.com/2026/04/16/14-04.html">&lt;h2 id=&quot;开场白&quot;&gt;开场白&lt;/h2&gt;

&lt;p&gt;Hacker News 上有个挺火的讨论，80 多条评论，一群人围着 OpenClaw 这个 AI Agent 工具聊得热火朝天。&lt;/p&gt;

&lt;p&gt;我花了一晚上爬完了所有评论，发现一个有意思的现象：&lt;strong&gt;同样是 AI 助手，有人拿它当管家，有人拿它当秘书，还有人拿它当学习伴侣。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;今天这篇文章，我就把 HN 上大家讨论最热烈的使用场景整理出来，挑出&lt;strong&gt;TOP 3 最佳场景&lt;/strong&gt;，每个场景都给你分析清楚利弊。&lt;/p&gt;

&lt;p&gt;看完你就知道，这只”小龙虾”到底能帮你干啥。&lt;/p&gt;

&lt;!-- [IMAGE: A cute red crayfish mascot wearing glasses, sitting in front of a computer screen displaying code and notes, digital art style, warm lighting, friendly and approachable] --&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;top-1obsidian-笔记自动化-&quot;&gt;TOP 1：Obsidian 笔记自动化 ⭐⭐⭐⭐⭐&lt;/h2&gt;

&lt;p&gt;这是 HN 上&lt;strong&gt;提及频率最高&lt;/strong&gt;的场景，没有之一。&lt;/p&gt;

&lt;h3 id=&quot;场景长什么样&quot;&gt;场景长什么样？&lt;/h3&gt;

&lt;p&gt;想象一下这个工作流：&lt;/p&gt;

&lt;p&gt;你白天在 Obsidian 里写了几篇笔记，可能是读书笔记、会议记录、或者学习笔记。晚上睡觉前，你什么都不用做，OpenClaw 会自动：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;拉取你私有 GitHub 仓库里的 Obsidian 更新&lt;/li&gt;
  &lt;li&gt;解析新增的笔记内容&lt;/li&gt;
  &lt;li&gt;用 LLM 提取关键知识点，生成闪卡（Flashcards）&lt;/li&gt;
  &lt;li&gt;把闪卡自动导入 Anki 或你的间隔重复系统&lt;/li&gt;
  &lt;li&gt;第二天早上，你打开闪卡 App，昨天学的内容已经变成可以复习的卡片了&lt;/li&gt;
&lt;/ol&gt;

&lt;!-- [IMAGE: A clean modern workspace with a laptop showing Obsidian notes on screen, flashcards floating above the keyboard like holograms, soft blue and white color scheme, minimalist tech illustration] --&gt;

&lt;p&gt;有位叫 dsiegel2275 的用户是这么说的：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“我每晚让 OpenClaw 拉取私有 GitHub 仓库的 Obsidian 笔记更新，然后运行’创建闪卡’skill 提取有用的闪卡用于间隔重复练习。第二天打开闪卡 App 时，已经有昨天课程的新卡片可以复习了。”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;更进阶的玩法是&lt;strong&gt;自动汇总&lt;/strong&gt;：日报 → 周报 → 月报，全自动生成。&lt;/p&gt;

&lt;h3 id=&quot;技术实现&quot;&gt;技术实现&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Git 同步 + Markdown 解析 + LLM 提取知识点&lt;/li&gt;
  &lt;li&gt;通过 API 将闪卡插入 Anki 或自定义系统&lt;/li&gt;
  &lt;li&gt;可选：自动提醒复习&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;利-&quot;&gt;利 ✅&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;真正的”无感”学习&lt;/strong&gt;：你只管写笔记，剩下的交给它&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;知识沉淀自动化&lt;/strong&gt;：不再担心笔记写完就吃灰&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;间隔重复无缝衔接&lt;/strong&gt;：学习曲线更平滑&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;弊-&quot;&gt;弊 ❌&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;需要一定的技术基础&lt;/strong&gt;：得会配置 Git 仓库、API keys&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;闪卡质量依赖 LLM&lt;/strong&gt;：有时候提取的知识点不够精准&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;初期设置耗时&lt;/strong&gt;：有用户反馈配置需要 20~30 步&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;适合谁&quot;&gt;适合谁？&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;重度 Obsidian 用户&lt;/li&gt;
  &lt;li&gt;有持续学习习惯的人&lt;/li&gt;
  &lt;li&gt;愿意花时间搭建自动化工作流的技术爱好者&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;top-2信息摘要与每日简报-&quot;&gt;TOP 2：信息摘要与每日简报 ⭐⭐⭐⭐&lt;/h2&gt;

&lt;p&gt;这是第二个热门场景，多位用户分享。&lt;/p&gt;

&lt;h3 id=&quot;场景长什么样-1&quot;&gt;场景长什么样？&lt;/h3&gt;

&lt;p&gt;你每天关心 Hacker News、Twitter 上的某些话题，但没时间一条条刷。OpenClaw 可以：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;定时抓取你指定的信息源（HN、Twitter、RSS 等）&lt;/li&gt;
  &lt;li&gt;根据你的兴趣主题筛选内容&lt;/li&gt;
  &lt;li&gt;用 LLM 生成摘要&lt;/li&gt;
  &lt;li&gt;在固定时间（比如每天早上 8 点）把简报发送到你的 Telegram/Slack/Discord&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;用户 lizardking 说：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“我让它每晚扫描 Hacker News 和 Twitter 上我感兴趣的主题，摘要故事和讨论。这是一个很棒的每日摘要。”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;还有个叫 jameson 的用户用它来：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“在预定时间总结 Top 10 Hacker News 或发送每日笑话。”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;更智能的是，它可以&lt;strong&gt;条件触发&lt;/strong&gt;——只有当有重要内容时才通知你，避免信息过载。&lt;/p&gt;

&lt;h3 id=&quot;技术实现-1&quot;&gt;技术实现&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;RSS/爬虫 + LLM 摘要 + 定时任务（Cron）&lt;/li&gt;
  &lt;li&gt;支持条件触发（仅在有重要内容时通知）&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;利--1&quot;&gt;利 ✅&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;信息过载的解药&lt;/strong&gt;：你只看摘要，不用刷原始信息流&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;个性化筛选&lt;/strong&gt;：只关注你真正关心的话题&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;时间可控&lt;/strong&gt;：固定时间推送，不打断工作流&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;弊--1&quot;&gt;弊 ❌&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;可能错过上下文&lt;/strong&gt;：摘要再精准，也可能漏掉重要细节&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Token 消耗大&lt;/strong&gt;：处理大量文本，成本不低&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;误判风险&lt;/strong&gt;：AI 可能把重要内容判定为”不重要”&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;适合谁-1&quot;&gt;适合谁？&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;信息焦虑症患者&lt;/li&gt;
  &lt;li&gt;需要追踪多个信息源的专业人士&lt;/li&gt;
  &lt;li&gt;想节省刷社交媒体时间的任何人&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;top-3邮件管理与自动化-&quot;&gt;TOP 3：邮件管理与自动化 ⭐⭐⭐⭐&lt;/h2&gt;

&lt;p&gt;第三个热门场景，尤其是对于商务人士。&lt;/p&gt;

&lt;h3 id=&quot;场景长什么样-2&quot;&gt;场景长什么样？&lt;/h3&gt;

&lt;p&gt;你的邮箱每天收到几十上百封邮件，OpenClaw 可以帮你：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;监控特定发件人或主题的邮件&lt;/li&gt;
  &lt;li&gt;自动分类和整理到不同文件夹&lt;/li&gt;
  &lt;li&gt;从发票 PDF 中提取数据，录入成本跟踪表&lt;/li&gt;
  &lt;li&gt;草稿回复邮件（需要你确认后再发送）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;用户 zsiddique 分享：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“我有自动化处理 incoming 邮件并执行任务。对于某些类型的邮件和问题，我已经有心理公式，现在交给 AI 处理。它可以快速回复，有时我会发现它偏离轨道（AI 数学还是有问题），但这些都是需要快速响应的事情。”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;还有个很实用的场景是&lt;strong&gt;日历检查&lt;/strong&gt;：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“我让它检查 Outlook 日历，告诉我是否有任何 1:1 会议对方已经拒绝（因为 Outlook 不清晰显示这个，我经常到会才发现对方取消了）。” — superfrank&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;技术实现-2&quot;&gt;技术实现&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;IMAP/邮件 API + 规则引擎 + LLM 分类&lt;/li&gt;
  &lt;li&gt;PDF 解析（发票等）&lt;/li&gt;
  &lt;li&gt;与日历 API 集成&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;利--2&quot;&gt;利 ✅&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;减少重复劳动&lt;/strong&gt;：分类、整理、提取数据，全自动&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;不错过重要邮件&lt;/strong&gt;：优先级排序，重要邮件优先处理&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;快速响应&lt;/strong&gt;：草稿自动写好，你只需要确认&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;弊--2&quot;&gt;弊 ❌&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;隐私风险&lt;/strong&gt;：邮件包含敏感信息，需谨慎授权&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AI 可能理解偏差&lt;/strong&gt;：尤其是复杂语境下的邮件&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;配置复杂&lt;/strong&gt;：不同邮件服务商 API 不同，需要分别配置&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;适合谁-2&quot;&gt;适合谁？&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;邮件量大的商务人士&lt;/li&gt;
  &lt;li&gt;需要处理大量发票/报销的创业者&lt;/li&gt;
  &lt;li&gt;经常开会、需要日历管理的职场人&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;其他值得关注的场景&quot;&gt;其他值得关注的场景&lt;/h2&gt;

&lt;p&gt;除了 TOP 3，还有一些场景也值得提一下：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;场景&lt;/th&gt;
      &lt;th&gt;热度&lt;/th&gt;
      &lt;th&gt;一句话描述&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;个人任务管理&lt;/td&gt;
      &lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
      &lt;td&gt;语音消息添加待办，像对真人助理说话&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;家庭自动化&lt;/td&gt;
      &lt;td&gt;⭐⭐⭐&lt;/td&gt;
      &lt;td&gt;Home Assistant 崩溃了？让机器人重启它&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;代码开发部署&lt;/td&gt;
      &lt;td&gt;⭐⭐⭐&lt;/td&gt;
      &lt;td&gt;记下一个 App 想法，几分钟后就部署好了&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;语言学习&lt;/td&gt;
      &lt;td&gt;⭐⭐⭐&lt;/td&gt;
      &lt;td&gt;日语学习 + Obsidian + Anki 一体化&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;健康数据追踪&lt;/td&gt;
      &lt;td&gt;⭐⭐&lt;/td&gt;
      &lt;td&gt;卡路里追踪、天气监控、心理健康提醒&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;有个场景我觉得特别有意思——&lt;strong&gt;心理健康支持&lt;/strong&gt;：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“一个记录你最近做的酷事的 agent，并主动提醒你，帮助你从更宏观的角度看问题，对我生活中因各种原因倾向于更负面认知模式的人很有帮助。” — piazz&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这只小龙虾，还能当你的情绪管家。&lt;/p&gt;

&lt;!-- [IMAGE: A friendly AI robot assistant helping a person organize emails and calendar on a smartphone, warm orange and teal colors, modern flat design illustration, professional yet approachable] --&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;用户怎么说&quot;&gt;用户怎么说？&lt;/h2&gt;

&lt;h3 id=&quot;正面评价&quot;&gt;正面评价&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;“便利性是能够在购物时要求它做某事，并让它自动测试等。” — BeetleB&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;“真正的解锁是保持在一个上下文中，不会迷失在琐碎细节的长尾中。” — ryanmcgarvey&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;“我有 GitHub 和 Vercel 连接，这意味着我可以记下一个小生产力 App 的想法，几分钟后它就出现并部署好了。” — ryanmcgarvey&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;负面评价&quot;&gt;负面评价&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;“初始设置比我想象的要耗时。OpenClaw 设置和配置是 20~30 步的情况，需要很多 API keys。” — godot&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;“潜力巨大。现实是令人沮丧和不可靠的。当它工作时，你真的很喜欢它。” — BeetleB&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Token 使用量不合理。要么使用更聪明的模型（成本更高），要么使用更便宜的模型（有时会陷入循环）。” — jameson&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;“OpenClaw 正好处于 prompt injection 致命三要素中。OpenClaw 实例能够重置你的账户密码听起来非常可怕。” — tcoff91&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;核心争议为什么不用脚本--cron&quot;&gt;核心争议：为什么不用脚本 + Cron？&lt;/h2&gt;

&lt;p&gt;这是整个讨论里最核心的争议点。&lt;/p&gt;

&lt;h3 id=&quot;支持-agent-的人说&quot;&gt;支持 Agent 的人说：&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;“写一个脚本和 crontab 条目需要更多努力，而 OpenClaw 开箱即用。”&lt;/li&gt;
  &lt;li&gt;“老板可能甚至不知道 Bash 是什么，更不用说 crontab 了。”&lt;/li&gt;
  &lt;li&gt;“是的，但我现在不需要问你（开发者）了。”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;支持脚本的人说&quot;&gt;支持脚本的人说：&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;“如果我可以 prompt AI 在几分钟内写一个实际的确定性脚本来解决问题，然后永远运行它，为什么要把它委托给非确定性 AI？”&lt;/li&gt;
  &lt;li&gt;“我更喜欢如果它不按预期工作时有东西可以调试。”&lt;/li&gt;
  &lt;li&gt;“那将是有效的，如果设置定时 AI 任务真的需要任何技术知识，但它不需要。ChatGPT 让你在 UI 中原生安排任务。”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;我的看法&quot;&gt;我的看法&lt;/h3&gt;

&lt;p&gt;这其实不是二选一的问题。&lt;strong&gt;核心业务用脚本，边缘任务用 Agent&lt;/strong&gt;，可能是更务实的选择。&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;总结小龙虾适合你吗&quot;&gt;总结：小龙虾适合你吗？&lt;/h2&gt;

&lt;h3 id=&quot;适合使用-openclaw-的场景&quot;&gt;适合使用 OpenClaw 的场景&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;非技术用户&lt;/strong&gt;需要自动化但不想学习脚本&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;快速原型&lt;/strong&gt;和实验性项目&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;个人助理&lt;/strong&gt;类任务（提醒、摘要、简单查询）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;学习/实验&lt;/strong&gt;Agent 技术和多 Agent 系统&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;不适合的场景&quot;&gt;不适合的场景&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;关键业务&lt;/strong&gt;流程（需要确定性）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;高价值/高风险&lt;/strong&gt;操作（银行、密码等）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;大规模生产&lt;/strong&gt;环境（成本不可控）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;需要审计/合规&lt;/strong&gt;的场景&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;最佳实践建议&quot;&gt;最佳实践建议&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;沙箱运行&lt;/strong&gt;：在 VPS 或容器中运行，限制访问权限&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;渐进式授权&lt;/strong&gt;：从低风险任务开始，逐步增加权限&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;监控与审计&lt;/strong&gt;：记录所有操作，定期检查&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;混合架构&lt;/strong&gt;：核心业务用脚本，边缘任务用 Agent&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;成本控制&lt;/strong&gt;：设置 Token 预算，使用便宜模型处理简单任务&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;最后说两句&quot;&gt;最后说两句&lt;/h2&gt;

&lt;p&gt;HN 上有位用户预测：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“我敢打赌几年后这将成为标准做法。” — bryan0&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;另一位说：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“这是一个很棒的工具。想象一下拥有你自己的管家，除了可靠性与可负担性还没有达到做任何严肃事情的程度。” — jameson&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI Agent 还在早期阶段，但它已经在改变一些人的工作方式了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;关键是找到适合你的场景，从小处开始，逐步扩展。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这只小龙虾能帮你干啥，最终取决于你想让它干啥。&lt;/p&gt;

&lt;hr /&gt;</content><author><name>mengtnt</name></author><summary type="html">开场白</summary></entry><entry><title type="html">Claude 三种 Agent 架构全解析：SDK vs Teams vs Managed 如何选择</title><link href="https://mengtnt.com/2026/04/12/01-04.html" rel="alternate" type="text/html" title="Claude 三种 Agent 架构全解析：SDK vs Teams vs Managed 如何选择" /><published>2026-04-12T00:00:00+00:00</published><updated>2026-04-12T00:00:00+00:00</updated><id>https://mengtnt.com/2026/04/12/01-04</id><content type="html" xml:base="https://mengtnt.com/2026/04/12/01-04.html">&lt;h1 id=&quot;claude-三种-agent-架构全解析sdk-vs-teams-vs-managed-如何选择&quot;&gt;Claude 三种 Agent 架构全解析：SDK vs Teams vs Managed 如何选择&lt;/h1&gt;

&lt;h2 id=&quot;引言&quot;&gt;引言&lt;/h2&gt;

&lt;p&gt;Anthropic 最近连续发布了三个与 Agent 相关的重磅产品：&lt;strong&gt;Agent SDK&lt;/strong&gt;、&lt;strong&gt;Agent Teams&lt;/strong&gt; 和 &lt;strong&gt;Managed Agents&lt;/strong&gt;。这一系列动作标志着 Claude 正式从”对话模型”进化为”Agent 平台”。&lt;/p&gt;

&lt;p&gt;对于开发者而言，这三个产品代表了三种截然不同的 Agent 构建范式。选择错误的架构可能导致数周的开发工作付诸东流。本文基于实际项目经验，深入剖析三种架构的技术细节、优缺点和适用场景，帮助你在项目中做出正确的选择。&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;一三种架构概览&quot;&gt;一、三种架构概览&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;特性&lt;/th&gt;
      &lt;th&gt;Agent SDK&lt;/th&gt;
      &lt;th&gt;Agent Teams&lt;/th&gt;
      &lt;th&gt;Managed Agents&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;控制粒度&lt;/td&gt;
      &lt;td&gt;最高&lt;/td&gt;
      &lt;td&gt;中等&lt;/td&gt;
      &lt;td&gt;最低&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;开发工作量&lt;/td&gt;
      &lt;td&gt;最大&lt;/td&gt;
      &lt;td&gt;中等&lt;/td&gt;
      &lt;td&gt;最小&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;状态管理&lt;/td&gt;
      &lt;td&gt;自己实现&lt;/td&gt;
      &lt;td&gt;共享 Task List&lt;/td&gt;
      &lt;td&gt;平台托管&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;错误处理&lt;/td&gt;
      &lt;td&gt;自己实现&lt;/td&gt;
      &lt;td&gt;部分自动&lt;/td&gt;
      &lt;td&gt;平台自动&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;适合场景&lt;/td&gt;
      &lt;td&gt;核心业务&lt;/td&gt;
      &lt;td&gt;长任务协作&lt;/td&gt;
      &lt;td&gt;简单任务&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;二agent-sdk完全控制的代价&quot;&gt;二、Agent SDK：完全控制的代价&lt;/h2&gt;

&lt;h3 id=&quot;21-架构原理&quot;&gt;2.1 架构原理&lt;/h3&gt;

&lt;p&gt;Agent SDK 是最早发布的方案，它提供了一套底层 API，让你自己编写完整的 agent loop。你需要处理：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Token 计数和截断&lt;/li&gt;
  &lt;li&gt;工具调用和结果解析&lt;/li&gt;
  &lt;li&gt;错误重试和回退&lt;/li&gt;
  &lt;li&gt;无限循环检测&lt;/li&gt;
  &lt;li&gt;中间状态持久化&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;22-代码示例&quot;&gt;2.2 代码示例&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;anthropic&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Agent&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyAgent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;agent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;claude-sonnet-4-20260514&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search_tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;code_tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;max_iterations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 70% 的代码是防御性编程
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# Token 超限检测
&lt;/span&gt;            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;180000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;truncate_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            
            &lt;span class=&quot;c1&quot;&gt;# 调用 Agent
&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;agent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            
            &lt;span class=&quot;c1&quot;&gt;# 无限循环检测
&lt;/span&gt;            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iteration_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_iterations&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AgentLoopError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Too many iterations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            
            &lt;span class=&quot;c1&quot;&gt;# 持久化中间结果
&lt;/span&gt;            &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
            
        &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ToolError&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# 工具失败重试
&lt;/span&gt;            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;retry_with_backoff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TokenLimitError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# Token 超限处理
&lt;/span&gt;            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compress_and_continue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;23-实际踩坑经验&quot;&gt;2.3 实际踩坑经验&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;坑 1：Token 超限&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Claude 的 context window 虽然大，但复杂任务很容易超限&lt;/li&gt;
  &lt;li&gt;必须实现智能截断策略：优先保留最近的消息和关键工具结果&lt;/li&gt;
  &lt;li&gt;建议设置软限制（180k）而非硬限制（200k）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;坑 2：工具调用失败&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;网络波动、API 限流、工具超时都会导致失败&lt;/li&gt;
  &lt;li&gt;必须实现指数退避重试&lt;/li&gt;
  &lt;li&gt;对于幂等操作可以重试，非幂等操作需要补偿机制&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;坑 3：无限循环&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Agent 可能陷入”调用工具→失败→再调用”的死循环&lt;/li&gt;
  &lt;li&gt;需要检测重复的工具调用模式&lt;/li&gt;
  &lt;li&gt;设置最大迭代次数并强制终止&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;24-优缺点&quot;&gt;2.4 优缺点&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;完全控制 Agent 行为&lt;/li&gt;
  &lt;li&gt;可以针对业务场景深度优化&lt;/li&gt;
  &lt;li&gt;不受平台配额限制&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;业务逻辑只占 30%，70% 是 harness 代码&lt;/li&gt;
  &lt;li&gt;开发周期长，维护成本高&lt;/li&gt;
  &lt;li&gt;需要深厚的工程经验&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;三agent-teams共享状态的协作范式&quot;&gt;三、Agent Teams：共享状态的协作范式&lt;/h2&gt;

&lt;h3 id=&quot;31-架构原理&quot;&gt;3.1 架构原理&lt;/h3&gt;

&lt;p&gt;Agent Teams 的核心创新是&lt;strong&gt;共享 Task List&lt;/strong&gt;。与传统子 Agent 的”fire-and-forget”模式不同：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;所有 Agent 共享同一个任务列表&lt;/li&gt;
  &lt;li&gt;Agent A 发现的问题可以直接添加到 Task List&lt;/li&gt;
  &lt;li&gt;Agent B 实时看到并可以接手处理&lt;/li&gt;
  &lt;li&gt;任务状态对所有 Agent 可见&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;32-代码示例&quot;&gt;3.2 代码示例&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;anthropic&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AgentTeam&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 创建共享任务列表
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;team&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AgentTeam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;claude-sonnet-4-20260514&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;agents&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;researcher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search_tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;coder&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;code_tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;reviewer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;review_tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shared_state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 关键：启用共享状态
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 所有 Agent 可以看到彼此的任务
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;team&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
完成以下项目：
1. 调研最新的 Agent 框架
2. 实现核心功能
3. 代码审查和优化
&quot;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Agent A (researcher) 可以在任务列表中添加新任务：
# &quot;发现依赖库 X 有安全问题，需要升级到 Y 版本&quot;
# Agent B (coder) 会立即看到并处理
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;33-与传统子-agent-的对比&quot;&gt;3.3 与传统子 Agent 的对比&lt;/h3&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;传统子 Agent 模式：
主 Agent → 子 Agent A (独立执行，返回结果)
        → 子 Agent B (独立执行，返回结果)
        
问题：子 Agent 之间无法通信，A 发现的问题 B 不知道

Agent Teams 模式：
共享 Task List:
- [✓] 调研框架 (Agent A 完成)
- [→] 实现功能 (Agent B 进行中)
- [!] 发现安全问题 (Agent A 添加，等待处理)
- [ ] 代码审查 (Agent C 待处理)

优势：所有 Agent 实时看到完整上下文
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;34-实际踩坑经验&quot;&gt;3.4 实际踩坑经验&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;坑 1：任务冲突&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;多个 Agent 可能同时处理同一个任务&lt;/li&gt;
  &lt;li&gt;需要实现任务锁或状态标记&lt;/li&gt;
  &lt;li&gt;建议设置”认领”机制&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;坑 2：状态同步延迟&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;共享状态不是完全实时的&lt;/li&gt;
  &lt;li&gt;关键决策前需要显式同步&lt;/li&gt;
  &lt;li&gt;避免依赖毫秒级一致性&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;35-优缺点&quot;&gt;3.5 优缺点&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;适合长任务、多阶段项目&lt;/li&gt;
  &lt;li&gt;Agent 之间可以自然协作&lt;/li&gt;
  &lt;li&gt;人类可以随时介入查看进度&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;共享状态增加复杂度&lt;/li&gt;
  &lt;li&gt;不适合完全独立的并行任务&lt;/li&gt;
  &lt;li&gt;调试难度较高&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;四managed-agents企业级稳定-agent-的加速器&quot;&gt;四、Managed Agents：企业级稳定 Agent 的加速器&lt;/h2&gt;

&lt;h3 id=&quot;41-架构原理&quot;&gt;4.1 架构原理&lt;/h3&gt;

&lt;p&gt;Managed Agents 不是为”简单任务”设计的玩具，而是&lt;strong&gt;为企业降低 Agent 开发成本、提升稳定性&lt;/strong&gt;而生的生产级方案。&lt;/p&gt;

&lt;p&gt;核心理念：&lt;strong&gt;让企业专注于业务逻辑，而非基础设施&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;你需要做的：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;定义业务 goal&lt;/li&gt;
  &lt;li&gt;提供领域 tools（API、数据库、内部系统）&lt;/li&gt;
  &lt;li&gt;平台处理所有工程复杂性&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;harness、memory、子 Agent 编排、错误恢复、状态持久化全部由平台托管。&lt;/p&gt;

&lt;h3 id=&quot;42-代码示例&quot;&gt;4.2 代码示例&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;anthropic&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ManagedAgent&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 企业级 Agent：客户服务自动化
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer_agent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ManagedAgent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;claude-sonnet-4-20260514&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;处理客户咨询，包括：
    1. 查询订单状态
    2. 处理退换货申请
    3. 解答常见问题
    4. 升级复杂问题到人工客服&quot;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;order_lookup_tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;      &lt;span class=&quot;c1&quot;&gt;# 内部订单系统 API
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;refund_tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# 退款系统 API
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;knowledge_base_tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# 企业知识库
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;ticket_escalation_tool&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 工单系统
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;memory_config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;persistent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 持久化记忆
&lt;/span&gt;        &lt;span class=&quot;s&quot;&gt;&quot;retention_days&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# 客户对话保留 90 天
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;compliance_config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;data_residency&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;EU&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 数据驻留要求
&lt;/span&gt;        &lt;span class=&quot;s&quot;&gt;&quot;audit_logging&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# 审计日志
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 生产环境部署
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer_agent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deploy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;production&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;scaling_policy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;auto&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 自动扩缩容
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;sla_target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;99.9&lt;/span&gt;         &lt;span class=&quot;c1&quot;&gt;# SLA 目标
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;43-企业级优势&quot;&gt;4.3 企业级优势&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. 稳定性保障&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;平台级错误恢复：自动重试、fallback、熔断&lt;/li&gt;
  &lt;li&gt;状态持久化：Agent 崩溃后可从中断点恢复&lt;/li&gt;
  &lt;li&gt;版本管理：Agent 配置可版本化、回滚&lt;/li&gt;
  &lt;li&gt;监控告警：内置性能监控和异常告警&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. 开发成本大幅降低&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;无需编写 harness 防御代码（节省 70% 开发量）&lt;/li&gt;
  &lt;li&gt;无需处理 token 管理、错误重试、无限循环检测&lt;/li&gt;
  &lt;li&gt;无需搭建监控、日志、追踪系统&lt;/li&gt;
  &lt;li&gt;小团队也能构建生产级 Agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. 企业合规支持&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;数据驻留控制（GDPR 合规）&lt;/li&gt;
  &lt;li&gt;审计日志（金融、医疗行业要求）&lt;/li&gt;
  &lt;li&gt;访问控制和权限管理&lt;/li&gt;
  &lt;li&gt;PII 数据自动脱敏&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. 可扩展性&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;自动扩缩容应对流量峰值&lt;/li&gt;
  &lt;li&gt;多区域部署降低延迟&lt;/li&gt;
  &lt;li&gt;与企业现有系统无缝集成&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;44-实际踩坑经验&quot;&gt;4.4 实际踩坑经验&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;坑 1：初期配置复杂度高&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;企业级功能需要详细配置（合规、审计、权限）&lt;/li&gt;
  &lt;li&gt;建议：从最小可行配置开始，逐步迭代&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;坑 2：自定义逻辑受限&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;核心循环逻辑由平台管理&lt;/li&gt;
  &lt;li&gt;非常规需求可能需要切换到 SDK&lt;/li&gt;
  &lt;li&gt;建议：80% 标准化流程用 Managed，20% 特殊场景用 SDK&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;坑 3：供应商锁定风险&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;深度依赖平台特性后迁移成本高&lt;/li&gt;
  &lt;li&gt;建议：关键业务逻辑保持模型无关，tools 层抽象化&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;45-优缺点&quot;&gt;4.5 优缺点&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;开发成本降低 70%+（无需 harness 代码）&lt;/li&gt;
  &lt;li&gt;生产级稳定性（平台级错误恢复、监控）&lt;/li&gt;
  &lt;li&gt;企业合规开箱即用&lt;/li&gt;
  &lt;li&gt;小团队也能构建复杂 Agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;核心逻辑控制力较弱&lt;/li&gt;
  &lt;li&gt;深度定制需求受限&lt;/li&gt;
  &lt;li&gt;存在供应商锁定风险&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;46-适用场景&quot;&gt;4.6 适用场景&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;强烈推荐：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;企业客户服务 Agent&lt;/li&gt;
  &lt;li&gt;内部知识库问答系统&lt;/li&gt;
  &lt;li&gt;标准化业务流程自动化&lt;/li&gt;
  &lt;li&gt;需要合规审计的场景（金融、医疗）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;不推荐：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;需要完全控制 Agent 行为的场景&lt;/li&gt;
  &lt;li&gt;非常规、实验性 Agent 设计&lt;/li&gt;
  &lt;li&gt;预算极其有限的小项目&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;五选型建议实战中的混搭策略&quot;&gt;五、选型建议：实战中的混搭策略&lt;/h2&gt;

&lt;p&gt;基于实际项目经验，推荐的架构选择策略：&lt;/p&gt;

&lt;h3 id=&quot;51-核心业务--agent-sdk&quot;&gt;5.1 核心业务 → Agent SDK&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;场景：&lt;/strong&gt; 支付处理、用户数据操作、关键业务流程&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;理由：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;需要完全控制错误处理&lt;/li&gt;
  &lt;li&gt;业务逻辑复杂，需要深度优化&lt;/li&gt;
  &lt;li&gt;不能接受平台配额限制&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# 核心支付流程必须用 SDK
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PaymentAgent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 自定义重试、补偿、审计日志
&lt;/span&gt;        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;retry_policy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ExponentialBackoff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_retries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;audit_log&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AuditLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process_payment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 100% 控制力
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;52-跨平台分发--传统子-agent-并行&quot;&gt;5.2 跨平台分发 → 传统子 Agent 并行&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;场景：&lt;/strong&gt; 同时发布到微信公众号、Twitter、博客&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;理由：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;各平台完全独立&lt;/li&gt;
  &lt;li&gt;不需要共享状态&lt;/li&gt;
  &lt;li&gt;并行执行效率最高&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# 完全独立的任务，用传统子 Agent
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;publish_to_wechat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;article&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;publish_to_twitter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;article&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;publish_to_blog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;article&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asyncio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gather&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;53-长任务复盘--agent-teams&quot;&gt;5.3 长任务复盘 → Agent Teams&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;场景：&lt;/strong&gt; 项目开发、代码重构、内容创作&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;理由：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;任务周期长，需要多阶段协作&lt;/li&gt;
  &lt;li&gt;可能需要人类中途介入&lt;/li&gt;
  &lt;li&gt;共享状态便于追踪进度&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# 长周期项目用 Teams
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project_team&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AgentTeam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;agents&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;planner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;executor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shared_state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project_team&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;重构用户认证模块&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;54-内部杂事--managed-agents&quot;&gt;5.4 内部杂事 → Managed Agents&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;场景：&lt;/strong&gt; 数据清洗、日志分析、简单报告&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;理由：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;任务简单标准化&lt;/li&gt;
  &lt;li&gt;开发效率优先&lt;/li&gt;
  &lt;li&gt;可以接受平台限制&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# 简单任务用 Managed
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ManagedAgent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;整理昨天的错误日志&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log_tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;六接入自定义模型能力&quot;&gt;六、接入自定义模型能力&lt;/h2&gt;

&lt;p&gt;虽然 Anthropic 的三种架构默认使用 Claude 模型，但在实际生产中，你可能需要：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;降低成本：用廉价模型处理简单任务&lt;/li&gt;
  &lt;li&gt;特定能力：某个模型在特定领域表现更好&lt;/li&gt;
  &lt;li&gt;数据合规：使用本地部署模型&lt;/li&gt;
  &lt;li&gt;避免供应商锁定：保持架构的模型无关性&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;61-sdk-架构完全灵活的模型接入&quot;&gt;6.1 SDK 架构：完全灵活的模型接入&lt;/h3&gt;

&lt;p&gt;SDK 的最大优势是可以无缝切换任意模型提供商。&lt;/p&gt;

&lt;h4 id=&quot;方案-a通过-openrouter-统一接入&quot;&gt;方案 A：通过 OpenRouter 统一接入&lt;/h4&gt;

&lt;p&gt;OpenRouter 提供统一的 API 格式，支持 100+ 模型：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;openai&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OpenAI&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# OpenRouter 兼容 OpenAI SDK
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OpenAI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;base_url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://openrouter.ai/api/v1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;YOUR_OPENROUTER_KEY&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MultiModelAgent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model_router&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;complex&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;anthropic/claude-sonnet-4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;simple&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;google/gemini-3-flash-preview&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;code&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;deepseek/deepseek-coder-v2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;local&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;local/llama-3-70b&quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 本地模型
&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;complexity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;medium&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model_router&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;complexity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;complex&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;tool_choice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;auto&quot;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse_agent_response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;一个 API 接入所有模型&lt;/li&gt;
  &lt;li&gt;按实际使用付费，无需预充值&lt;/li&gt;
  &lt;li&gt;自动 fallback 机制&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;成本对比（每 1M tokens）：&lt;/strong&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;模型&lt;/th&gt;
      &lt;th&gt;输入&lt;/th&gt;
      &lt;th&gt;输出&lt;/th&gt;
      &lt;th&gt;适用场景&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Claude Sonnet 4&lt;/td&gt;
      &lt;td&gt;$3&lt;/td&gt;
      &lt;td&gt;$15&lt;/td&gt;
      &lt;td&gt;核心业务&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Gemini 3 Flash&lt;/td&gt;
      &lt;td&gt;$0.075&lt;/td&gt;
      &lt;td&gt;$0.3&lt;/td&gt;
      &lt;td&gt;简单任务&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DeepSeek Coder&lt;/td&gt;
      &lt;td&gt;$0.14&lt;/td&gt;
      &lt;td&gt;$0.28&lt;/td&gt;
      &lt;td&gt;代码生成&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Llama 3 70B (本地)&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
      &lt;td&gt;数据敏感&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;方案-b本地模型部署ollamavllm&quot;&gt;方案 B：本地模型部署（Ollama/vLLM）&lt;/h4&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;openai&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OpenAI&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 本地 Ollama 服务
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;local_client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OpenAI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;base_url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://localhost:11434/v1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ollama&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HybridAgent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 云端 Claude 处理复杂任务
&lt;/span&gt;        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cloud_client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Anthropic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 本地模型处理敏感数据
&lt;/span&gt;        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;local_client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;local_client&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process_sensitive_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 敏感数据不出本地
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;local_client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;llama3:70b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;choices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;complex_reasoning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 复杂推理用 Claude
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cloud_client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;claude-sonnet-4-20260514&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;max_tokens&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4096&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;方案-c模型路由策略&quot;&gt;方案 C：模型路由策略&lt;/h4&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SmartRouter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;models&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;claude&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AnthropicClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;gemini&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GeminiClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;local&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OllamaClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;select_model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 根据任务特征选择模型
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sensitive&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;local&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;complexity&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;claude&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;code&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;claude&quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 或 deepseek
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;gemini&quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 便宜够用
&lt;/span&gt;    
    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;select_model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;models&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;62-agent-teams有限的模型自定义&quot;&gt;6.2 Agent Teams：有限的模型自定义&lt;/h3&gt;

&lt;p&gt;Agent Teams 目前&lt;strong&gt;仅支持 Claude 模型&lt;/strong&gt;，但可以通过以下方式变通：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# 变通方案：外部模型作为&quot;工具&quot;
&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;anthropic&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AgentTeam&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;requests&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call_local_model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;作为工具调用的本地模型&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;http://localhost:11434/api/generate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;llama3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;prompt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;response&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;team&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AgentTeam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;claude-sonnet-4-20260514&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;agents&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;researcher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search_tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 本地模型作为工具被 Claude 调用
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;Agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;local_processor&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;call_local_model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;限制：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;主模型必须是 Claude&lt;/li&gt;
  &lt;li&gt;其他模型只能作为工具被调用&lt;/li&gt;
  &lt;li&gt;无法让某个 Agent 完全使用非 Claude 模型&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;63-managed-agents不支持自定义模型&quot;&gt;6.3 Managed Agents：不支持自定义模型&lt;/h3&gt;

&lt;p&gt;Managed Agents &lt;strong&gt;完全绑定 Claude 模型&lt;/strong&gt;，无法切换。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;应对策略：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;简单任务用 Managed + Claude（省心）&lt;/li&gt;
  &lt;li&gt;需要自定义模型的任务降级为 SDK 架构&lt;/li&gt;
  &lt;li&gt;在 SDK 层实现模型路由，Managed 层只处理标准化流程&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;64-实战建议混合模型架构&quot;&gt;6.4 实战建议：混合模型架构&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ProductionAgentSystem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;生产环境的混合模型架构&quot;&quot;&quot;&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 1. SDK 层：核心业务，支持任意模型
&lt;/span&gt;        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;core_agent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SmartRouter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        
        &lt;span class=&quot;c1&quot;&gt;# 2. Teams 层：协作任务，用 Claude
&lt;/span&gt;        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project_team&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AgentTeam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;claude-sonnet-4-20260514&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;agents&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;c1&quot;&gt;# 3. Managed 层：简单任务，用 Claude
&lt;/span&gt;        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;simple_tasks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ManagedAgent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;日志分析&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ManagedAgent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;数据清洗&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;requires_custom_model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;core_agent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;long_running&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project_team&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;simple_tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;成本优化效果（月处理 100 万 tokens）：&lt;/strong&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;策略&lt;/th&gt;
      &lt;th&gt;纯 Claude&lt;/th&gt;
      &lt;th&gt;混合模型&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;月成本&lt;/td&gt;
      &lt;td&gt;~$10&lt;/td&gt;
      &lt;td&gt;~$3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;性能&lt;/td&gt;
      &lt;td&gt;100%&lt;/td&gt;
      &lt;td&gt;95%+&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;灵活性&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;七架构对比总结&quot;&gt;七、架构对比总结&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;维度&lt;/th&gt;
      &lt;th&gt;SDK&lt;/th&gt;
      &lt;th&gt;Teams&lt;/th&gt;
      &lt;th&gt;Managed&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;开发时间&lt;/td&gt;
      &lt;td&gt;2-4 周&lt;/td&gt;
      &lt;td&gt;1-2 周&lt;/td&gt;
      &lt;td&gt;1-2 天&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;控制力&lt;/td&gt;
      &lt;td&gt;★★★★★&lt;/td&gt;
      &lt;td&gt;★★★☆☆&lt;/td&gt;
      &lt;td&gt;★☆☆☆☆&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;维护成本&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;适合团队&lt;/td&gt;
      &lt;td&gt;资深工程师&lt;/td&gt;
      &lt;td&gt;中级工程师&lt;/td&gt;
      &lt;td&gt;任何开发者&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;配额限制&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
      &lt;td&gt;中等&lt;/td&gt;
      &lt;td&gt;严格&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;结语&quot;&gt;结语&lt;/h2&gt;

&lt;p&gt;Anthropic 发布的三个 Agent 产品，不是让你三选一，而是提供了一套完整的工具箱：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;SDK&lt;/strong&gt; 是手术刀——精确、强大，但需要高超技艺&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Teams&lt;/strong&gt; 是协作平台——适合团队作战的长周期项目&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Managed&lt;/strong&gt; 是自动化工具——简单任务的首选&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;明智的开发者会根据场景混搭使用：核心业务用 SDK 保证控制力，跨平台任务用并行子 Agent 提升效率，长周期项目用 Teams 便于协作，日常杂事用 Managed 节省精力。&lt;/p&gt;

&lt;p&gt;记住：&lt;strong&gt;架构没有银弹，只有取舍&lt;/strong&gt;。选择最适合你当前场景的方案，而不是最酷的方案。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;本文基于实际项目经验总结，欢迎交流讨论。&lt;/em&gt;&lt;/p&gt;</content><author><name>mengtnt</name></author><summary type="html">Claude 三种 Agent 架构全解析：SDK vs Teams vs Managed 如何选择</summary></entry><entry><title type="html">Sora 退场，Seedance 进场</title><link href="https://mengtnt.com/2026/04/03/15-45.html" rel="alternate" type="text/html" title="Sora 退场，Seedance 进场" /><published>2026-04-03T07:45:35+00:00</published><updated>2026-04-03T07:45:35+00:00</updated><id>https://mengtnt.com/2026/04/03/15-45</id><content type="html" xml:base="https://mengtnt.com/2026/04/03/15-45.html">&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;摘要&lt;/strong&gt;：2026 年初，OpenAI 宣布暂停 Sora 的公开服务，而字节跳动旗下的 Seedance 2.0 却正式开启企业级公测。这一”一退一进”的背后，折射出中美两国在 AI 视频生成领域的不同战略选择。本文从技术瓶颈与成本、版权合规、地缘竞争三个维度，深度剖析两种商业化路径的底层逻辑。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;引言冰火两重天的-2026&quot;&gt;引言：冰火两重天的 2026&lt;/h2&gt;

&lt;p&gt;2026 年 3 月，AI 视频生成赛道上演了一幕颇具戏剧性的”换角戏”。&lt;/p&gt;

&lt;p&gt;一边是曾被寄予厚望的 &lt;strong&gt;OpenAI Sora&lt;/strong&gt; 悄然宣布”暂停服务”，官方声明中仅以”技术迭代与安全检查”为由，未透露复出时间表。另一边，字节跳动旗下的 &lt;strong&gt;Seedance 2.0&lt;/strong&gt; 在武汉高调宣布开启企业级公测，火山引擎”2026 Force Link AI 创新巡展”现场人头攒动，API 申请通道瞬间涌入数千家企业认证主体。&lt;/p&gt;

&lt;p&gt;这一”一退一进”，绝非偶然。它背后折射出的是中美两国在 AI 视频生成领域的&lt;strong&gt;不同战略判断&lt;/strong&gt;、&lt;strong&gt;不同合规路径&lt;/strong&gt;，以及&lt;strong&gt;不同商业化节奏&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;本文将从三个核心维度展开分析：&lt;strong&gt;大模型视频能力的技术瓶颈与成本结构&lt;/strong&gt;、&lt;strong&gt;版权与肖像权的合规策略&lt;/strong&gt;、&lt;strong&gt;中美科技竞争背景下的产业博弈&lt;/strong&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;一技术瓶颈与成本理想很丰满现实很骨感&quot;&gt;一、技术瓶颈与成本：理想很丰满，现实很骨感&lt;/h2&gt;

&lt;h3 id=&quot;11-视频生成的算力黑洞&quot;&gt;1.1 视频生成的”算力黑洞”&lt;/h3&gt;

&lt;p&gt;AI 视频生成被业内戏称为”算力黑洞”。与文本生成（LLM）和图像生成（Diffusion）不同，视频生成需要同时处理&lt;strong&gt;时间维度的一致性&lt;/strong&gt;和&lt;strong&gt;空间维度的细节&lt;/strong&gt;，这对算力提出了指数级增长的需求。&lt;/p&gt;

&lt;p&gt;根据公开数据，生成一段&lt;strong&gt;60 秒 1080p&lt;/strong&gt; 的视频，Sora 需要消耗约&lt;strong&gt;12,000 个 A100 GPU 小时&lt;/strong&gt;的算力资源。即使按批发价计算，单次生成的算力成本也高达&lt;strong&gt;30-50 美元&lt;/strong&gt;。这还不包括模型训练、推理优化、存储带宽等隐性成本。&lt;/p&gt;

&lt;p&gt;Seedance 2.0 的定价策略则更为”务实”。根据火山引擎公布的公测政策，企业用户生成一段&lt;strong&gt;2 分钟科幻短片&lt;/strong&gt;的成本约为&lt;strong&gt;330 元人民币（约 45 美元）&lt;/strong&gt;。看似与 Sora 相近，但 Seedance 提供了&lt;strong&gt;分层计费&lt;/strong&gt;和&lt;strong&gt;并发优化&lt;/strong&gt;机制：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;计费维度&lt;/th&gt;
      &lt;th&gt;Sora（暂停前）&lt;/th&gt;
      &lt;th&gt;Seedance 2.0&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;基础生成&lt;/td&gt;
      &lt;td&gt;按秒计费，无折扣&lt;/td&gt;
      &lt;td&gt;按 Token 计费，量大从优&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;并发限制&lt;/td&gt;
      &lt;td&gt;单账户 5 并发&lt;/td&gt;
      &lt;td&gt;企业认证 10 并发，可扩容&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;定制功能&lt;/td&gt;
      &lt;td&gt;不开放&lt;/td&gt;
      &lt;td&gt;需 100 万保证金 + 保底协议&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;退款机制&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
      &lt;td&gt;保证金期满可返还&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;这种差异反映了两种不同的&lt;strong&gt;成本转嫁策略&lt;/strong&gt;：Sora 试图通过统一高价覆盖所有用户，而 Seedance 则通过”保证金 + 分层”将成本压力转移给有支付能力的企业客户。&lt;/p&gt;

&lt;h3 id=&quot;12-技术瓶颈的天花板效应&quot;&gt;1.2 技术瓶颈的”天花板效应”&lt;/h3&gt;

&lt;p&gt;2025 年下半年，多家 AI 实验室内部评估报告显示，当前视频生成模型在以下方面遭遇瓶颈：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;长视频一致性&lt;/strong&gt;：超过 3 分钟的视频，角色面部特征漂移率超过 15%&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;物理规律模拟&lt;/strong&gt;：复杂流体力学、光影反射等场景仍有明显瑕疵&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;多镜头叙事&lt;/strong&gt;：场景切换时的连贯性不足，需要人工后期修补&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OpenAI 内部人士透露，Sora 暂停服务的核心原因之一是&lt;strong&gt;技术迭代遇到瓶颈&lt;/strong&gt;。在达到”工业可用”标准之前，继续开放服务只会消耗大量算力却无法产生商业回报。&lt;/p&gt;

&lt;p&gt;反观字节跳动，其策略更为”渐进式”。Seedance 2.0 明确&lt;strong&gt;限制视频时长&lt;/strong&gt;（公测版单段不超过 5 分钟），并&lt;strong&gt;禁用真人人脸生成&lt;/strong&gt;，从而规避了最耗算力和最易引发争议的场景。这种”先做减法，再做加法”的策略，使得 Seedance 能够在可控成本下实现商业化闭环。&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;二版权问题中美不同的合规护城河&quot;&gt;二、版权问题：中美不同的合规”护城河”&lt;/h2&gt;

&lt;h3 id=&quot;21-美国的诉讼驱动模式&quot;&gt;2.1 美国的”诉讼驱动”模式&lt;/h3&gt;

&lt;p&gt;Sora 的暂停，版权诉讼是重要诱因之一。&lt;/p&gt;

&lt;p&gt;2025 年 12 月，好莱坞编剧工会（WGA）与美国演员工会（SAG-AFTRA）联合发起集体诉讼，指控 Sora 在训练过程中使用了&lt;strong&gt;未经授权的影视作品片段&lt;/strong&gt;。诉讼文件显示，OpenAI 无法提供完整的训练数据来源清单，这成为其”致命软肋”。&lt;/p&gt;

&lt;p&gt;此外，多位知名导演（包括诺兰、卡梅隆等）公开表态，认为 AI 生成视频”侵犯了人类创作者的署名权与改编权”。在法律压力与舆论压力的双重夹击下，OpenAI 选择了”以退为进”。&lt;/p&gt;

&lt;h3 id=&quot;22-中国的预审--水印模式&quot;&gt;2.2 中国的”预审 + 水印”模式&lt;/h3&gt;

&lt;p&gt;相比之下，Seedance 2.0 的合规策略更为”前置化”：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;训练数据白名单&lt;/strong&gt;：字节跳动宣布其训练数据全部来自”已授权内容库”，包括抖音、西瓜视频、即梦平台等自有生态，以及与部分影视公司的合作协议。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;C2PA 水印技术&lt;/strong&gt;：所有生成视频自动嵌入&lt;strong&gt;不可见数字水印&lt;/strong&gt;，可追溯生成时间、生成主体、使用模型版本等信息。这符合欧盟《AI 法案》的透明度要求，也为未来可能的版权纠纷提供证据链。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;真人肖像”双锁”机制&lt;/strong&gt;：公测版默认禁用真人人脸生成。企业如需解锁，需缴纳&lt;strong&gt;100 万元保证金&lt;/strong&gt;并签署《肖像权合规承诺书》，一旦违规，保证金全额扣除并承担法律责任。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这种”预审 + 水印 + 保证金”的三重机制，实质上是将&lt;strong&gt;合规成本内部化&lt;/strong&gt;，让企业用户成为版权保护的”第一责任人”。&lt;/p&gt;

&lt;h3 id=&quot;23-合规成本的隐性门槛&quot;&gt;2.3 合规成本的”隐性门槛”&lt;/h3&gt;

&lt;p&gt;值得注意的是，Seedance 的合规策略虽然降低了法律风险，但也抬高了&lt;strong&gt;市场准入门槛&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;100 万元保证金对于大型影视公司而言可能只是”九牛一毛”，但对于中小微内容创作者、独立工作室而言，却是&lt;strong&gt;难以逾越的门槛&lt;/strong&gt;。这导致 Seedance 的公测用户结构呈现明显的”头部集中”特征——目前通过企业认证的客户中，超过 60% 为注册资本超过 1000 万元的企业。&lt;/p&gt;

&lt;p&gt;这种策略的长期影响尚待观察：它可能促进内容产业的”集约化”，也可能抑制创新生态的”多样性”。&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;三中美竞争ai-视频赛道的地缘博弈&quot;&gt;三、中美竞争：AI 视频赛道的”地缘博弈”&lt;/h2&gt;

&lt;h3 id=&quot;31-技术封锁与去美化供应链&quot;&gt;3.1 技术封锁与”去美化”供应链&lt;/h3&gt;

&lt;p&gt;2025 年以来，美国对华 AI 芯片出口管制持续收紧。NVIDIA A100/H100 等高端 GPU 的对华出口基本冻结，这直接影响了中国 AI 企业的&lt;strong&gt;训练效率&lt;/strong&gt;与&lt;strong&gt;推理成本&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;在此背景下，Seedance 2.0 选择了一条”差异化竞争”路线：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;模型架构优化&lt;/strong&gt;：采用华为昇腾 910B 等国产芯片进行推理加速，降低对 NVIDIA 生态的依赖。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;应用场景聚焦&lt;/strong&gt;：优先落地于短视频、电商营销、教育培训等”轻量级”场景，而非好莱坞级别的”重工业”制作。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;市场区域选择&lt;/strong&gt;：国际推广首选东南亚、拉美等”非敏感”市场，暂时避开欧美监管高压区。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;32-数据生态的护城河效应&quot;&gt;3.2 数据生态的”护城河效应”&lt;/h3&gt;

&lt;p&gt;字节跳动的核心优势在于其&lt;strong&gt;庞大的内容生态&lt;/strong&gt;。抖音、今日头条、西瓜视频等平台每天产生&lt;strong&gt;数亿条视频内容&lt;/strong&gt;，这为 Seedance 提供了得天独厚的训练数据与应用场景。&lt;/p&gt;

&lt;p&gt;相比之下，OpenAI 的数据来源更为”分散”。虽然其拥有 GPT 系列模型的文本数据优势，但在视频领域的”原生数据”积累远不如字节跳动。这也是 Sora 在”多镜头叙事一致性”上表现不如 Seedance 的原因之一。&lt;/p&gt;

&lt;h3 id=&quot;33-商业化节奏的战略耐心&quot;&gt;3.3 商业化节奏的”战略耐心”&lt;/h3&gt;

&lt;p&gt;OpenAI 作为私营公司，面临来自微软等投资方的&lt;strong&gt;盈利压力&lt;/strong&gt;。Sora 的高成本与低回报，使其在商业上难以持续。暂停服务，某种程度上是”止损”的理性选择。&lt;/p&gt;

&lt;p&gt;字节跳动则展现出更强的”战略耐心”。Seedance 2.0 的公测政策明确表示，&lt;strong&gt;2026 年内不以盈利为目标&lt;/strong&gt;，而是以”生态培育”和”场景验证”为核心。这种”先占市场，再谈盈利”的策略，与中国互联网行业的传统打法一脉相承。&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;四结论与展望谁能笑到最后&quot;&gt;四、结论与展望：谁能笑到最后？&lt;/h2&gt;

&lt;p&gt;Sora 的退场与 Seedance 的进场，并非简单的”胜负之分”，而是两种&lt;strong&gt;不同发展阶段&lt;/strong&gt;、&lt;strong&gt;不同市场环境&lt;/strong&gt;、&lt;strong&gt;不同战略选择&lt;/strong&gt;的体现。&lt;/p&gt;

&lt;h3 id=&quot;41-短期判断2026-2027&quot;&gt;4.1 短期判断（2026-2027）&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Seedance 将占据中国市场主导地位&lt;/strong&gt;，尤其在短视频、电商、教育等垂直场景。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Sora 可能在 2027 年以”企业版”形式回归&lt;/strong&gt;，但定价将大幅提高，面向高端影视制作市场。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;开源模型将填补中低端市场空白&lt;/strong&gt;，如 Stability AI 的 Stable Video、Meta 的 Movie Gen 等。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;42-长期展望2028-及以后&quot;&gt;4.2 长期展望（2028 及以后）&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;技术融合&lt;/strong&gt;：文本、图像、视频、音频的”多模态一体化”将成为标配，单一模态模型将被淘汰。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;监管趋同&lt;/strong&gt;：中美欧三方将在 AI 版权、水印、溯源等方面形成”最低共识”，跨境合规成本下降。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;成本下降&lt;/strong&gt;：随着芯片效率提升与模型优化，视频生成成本有望下降&lt;strong&gt;1-2 个数量级&lt;/strong&gt;，个人创作者将成为主力用户。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;43-给从业者的建议&quot;&gt;4.3 给从业者的建议&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;不要押注单一模型&lt;/strong&gt;：多模型冗余部署，降低供应链风险。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;关注合规前置&lt;/strong&gt;：版权、肖像、水印等合规成本应纳入预算。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;深耕垂直场景&lt;/strong&gt;：通用视频生成竞争惨烈，垂直场景（如医疗、法律、工业）仍有蓝海。&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;结语&quot;&gt;结语&lt;/h2&gt;

&lt;p&gt;AI 视频生成的”黄金时代”才刚刚开始。Sora 的退场不是终点，Seedance 的进场也不是终点。真正的赢家，将是那些能够&lt;strong&gt;平衡技术、成本、合规&lt;/strong&gt;三者关系的玩家。&lt;/p&gt;

&lt;p&gt;对于内容创作者而言，这是一个最好的时代——工具从未如此强大；这也是一个最坏的时代——规则从未如此复杂。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;唯有拥抱变化者，方能立于不败之地。&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;</content><author><name>mengtnt</name></author><summary type="html">摘要：2026 年初，OpenAI 宣布暂停 Sora 的公开服务，而字节跳动旗下的 Seedance 2.0 却正式开启企业级公测。这一”一退一进”的背后，折射出中美两国在 AI 视频生成领域的不同战略选择。本文从技术瓶颈与成本、版权合规、地缘竞争三个维度，深度剖析两种商业化路径的底层逻辑。</summary></entry><entry><title type="html">Harness 设计</title><link href="https://mengtnt.com/2026/03/31/Harness-Design.html" rel="alternate" type="text/html" title="Harness 设计" /><published>2026-03-31T08:28:18+00:00</published><updated>2026-03-31T08:28:18+00:00</updated><id>https://mengtnt.com/2026/03/31/Harness-Design</id><content type="html" xml:base="https://mengtnt.com/2026/03/31/Harness-Design.html">&lt;h1 id=&quot;harness-设计构建长时间运行的应用程序开发框架&quot;&gt;Harness 设计：构建长时间运行的应用程序开发框架&lt;/h1&gt;

&lt;p&gt;本文是阅读 Harness 框架这篇文章后，做的一些简单的摘要总结，再配合着最近泄漏的 claud code 源码，Anthropics 真是给广大开发者送福利了。&lt;/p&gt;

&lt;p&gt;过去几个月里，我们一直在研究两个相互关联的问题：如何让 Claude 产出高质量的前端设计，以及如何让它在无需人工干预的情况下构建完整的应用程序。这项工作源于我们早期在&lt;a href=&quot;https://github.com/anthropics/claude-code/blob/main/plugins/frontend-design/skills/frontend-design/SKILL.md&quot;&gt;前端设计技能&lt;/a&gt;和&lt;a href=&quot;https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents&quot;&gt;长时运行编码 Agent 框架&lt;/a&gt;上的努力。通过提示词工程和框架（Harness）设计，我们能够将 Claude 的性能提升到远超基准线的水平，但这两者最终都遇到了瓶颈。&lt;/p&gt;

&lt;p&gt;为了突破瓶颈，我们寻求了一种新颖的 AI 工程方法，该方法横跨了两个完全不同的领域：一个由主观审美定义，另一个由可验证的正确性和可用性定义。受&lt;a href=&quot;https://en.wikipedia.org/wiki/Generative_adversarial_network&quot;&gt;生成对抗网络&lt;/a&gt;（GANs）的启发，我们设计了一个由 &lt;strong&gt;生成器(Generator)&lt;/strong&gt; 和 &lt;strong&gt;评估器(Evaluator)&lt;/strong&gt;  Agent 组成的多智能体结构。构建一个能够可靠且具备审美能力地对输出进行评分的评估器，意味着首先要开发一套标准，将诸如“这个设计好吗？”之类的主观判断转化为具体的、可量化的术语。
随后，我们将这些技术应用到了长时运行的自主编码中，并借鉴了早期框架工作的两个教训：将构建任务分解为可处理的代码块，以及使用结构化的“成品”在不同 Session 之间传递上下文。最终结果是一个三智能体架构——计划者（Planner）、生成器（Generator）和评估器（Evaluator）——它能在长达数小时的自主编码 Session 中构建出功能丰富、视觉精美的全栈应用。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“框架（Harness）的设计对长时运行的 Agent 编码有效性有着实质性影响。”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;为什么-agent-的实现会失败&quot;&gt;为什么 Agent 的实现会失败？&lt;/h2&gt;

&lt;p&gt;我们之前已经证明，Harness 的设计对长时运行 Agent 编码的有效性有着实质性影响。在早期的&lt;a href=&quot;https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents&quot;&gt;实验&lt;/a&gt;中，我们使用一个初始 Agent 将产品规格书分解为任务列表，再由一个编码 Agent 逐一实现功能，最后通过转移成品来跨 Session 携带状态。开发者社区也得出了类似的见解，例如使用钩子或脚本让 Agent 处于持续迭代循环中的方法。&lt;/p&gt;

&lt;p&gt;但一些核心问题依然存在。对于更复杂的任务，Agent 随着时间的推移仍容易“跑偏”。在分析这个问题时，我们观察到了两种常见的失败模式。&lt;/p&gt;

&lt;h3 id=&quot;1-连贯性丢失与上下文焦虑&quot;&gt;1. 连贯性丢失与“上下文焦虑”&lt;/h3&gt;

&lt;p&gt;首先是模型在长任务中往往会丧失连贯性。随着上下文窗口被填满，一些模型还会表现出“上下文焦虑”——当它们认为接近上下文限制时，会过早地试图收尾工作。&lt;strong&gt;上下文重置（Context Resets）&lt;/strong&gt;——即完全清空上下文窗口并启动一个全新的 Agent，配合携带前序状态和后续计划的结构化移交——可以同时解决这两个问题。&lt;/p&gt;

&lt;p&gt;这与单纯的“压缩”（Compaction）不同。压缩保留了连贯性，但没有提供“干净的底板”，这意味着上下文焦虑依然可能存在。重置则提供了一个干净的起点，代价是移交制品必须包含足够的信息，以便下一个 Agent 能够无缝接手。&lt;/p&gt;

&lt;h3 id=&quot;2-自我评价的盲目性&quot;&gt;2. 自我评价的盲目性&lt;/h3&gt;

&lt;p&gt;第二个问题是自我评价。当被要求评价自己产出的工作时，Agent 往往会自信地赞美自己——即使在人类观察者看来，质量明显平庸。这在设计等主观任务中尤为严重，因为这里不存在像软件测试那样的二进制校验。&lt;/p&gt;

&lt;p&gt;将“干活的 Agent”与“评价的 Agent”分开，是解决这一问题的强力杠杆。这种分离本身并不能立即消除各种宽容；评估器依然是一个倾向于对 LLM 生成内容表现慷慨的大模型。但将一个独立的评估器调校得“挑剔且多疑”，要比让一个生成器批判自己的工作容易得多。一旦这种外部反馈存在，生成器就有了一个具体的迭代目标。&lt;/p&gt;

&lt;h2 id=&quot;前端设计让主观质量变得可量化&quot;&gt;前端设计：让主观质量变得可量化&lt;/h2&gt;

&lt;p&gt;我们首先在前端设计上做了实验，因为自我评价问题在这里最为明显。如果没有干预，Claude 通常会倾向于安全、可预测的布局，这些布局在技术上可行，但在视觉上平庸。&lt;/p&gt;

&lt;p&gt;我为前端设计编写了四个评估标准：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;设计质量&lt;/strong&gt;：设计是否感觉是一个连贯的整体而非零件的堆砌？色彩、排版、布局、意向等细节是否结合并创造了独特的氛围和身份？&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;原创性&lt;/strong&gt;：是否有自定义决策的证据？人类设计师应该能识别出刻意的创意选择。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;精致性&lt;/strong&gt;：技术执行力，包括层级、间距一致性、色彩和谐度、对比度。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;功能性&lt;/strong&gt;：独立于审美的可用性。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们特别强调了设计质量和原创性，因为 Claude 本身在工艺和功能性上的得分通常很高。标准中明确惩罚了高度通用的“AI 垃圾（AI slop）”模式，迫使模型在审美上进行更多冒险。&lt;/p&gt;

&lt;h2 id=&quot;扩展到全栈编码三智能体协作&quot;&gt;扩展到全栈编码：三智能体协作&lt;/h2&gt;

&lt;p&gt;基于这些发现，我将这种受 GAN 启发的模式应用到了全栈开发中。系统包含以下三种角色：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;计划者 (Planner)&lt;/strong&gt;：将模糊的指令（1-4 句话）扩展为完整的产品规格（Spec）。它专注于产品背景和高层技术设计，而不是详细的实现细节。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;生成器 (Generator)&lt;/strong&gt;：采用“冲刺（Sprint）”模式，每次只根据 Spec 开发一个特定功能。它还拥有 Git 权限用于版本控制。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;评估器 (Evaluator)&lt;/strong&gt;：这是核心。它使用 Playwright MCP 像真实用户一样点击运行中的应用，测试 UI、API 端点和数据库状态。如果任何一项标准低于阈值，冲刺即宣告失败，生成器会收到详细的 Bug 反馈。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在每次执行前，生成器和评估器会协商一份 &lt;strong&gt;冲刺合约&lt;/strong&gt; ：在写代码前双方便达成共识，确定“完成”的定义。&lt;/p&gt;

&lt;h2 id=&quot;实验从-20-分钟到-6-小时的质变&quot;&gt;实验：从 20 分钟到 6 小时的质变&lt;/h2&gt;

&lt;p&gt;我们让模型尝试创建一个“2D 复古游戏制作工具”。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;框架&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;运行时间&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;总成本&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;结果质量&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;单模型 (Solo)&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;20 分钟&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;$9&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;布局浪费空间，流程死板，且游戏引擎库是坏的。&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;全套 Harness&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;6 小时&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;$200&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;极其精美，功能包含精灵编辑、动画系统、AI 辅助设计。最重要的是，真的可以玩。&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;这种差异是立竿见影的。全套 Harness 构建的应用具备了一致的视觉特征和极高的完成度。评估器通过 Playwright 进行了极其细致的测试，例如：“矩形填充工具在鼠标抬起时未能触发”、“FastAPI 路由冲突”等细微 Bug 均被捕捉并修复。&lt;/p&gt;

&lt;h2 id=&quot;简单化与未来演进&quot;&gt;简单化与未来演进&lt;/h2&gt;

&lt;p&gt;随着 Claude Opus 4.6 等更强模型的推出，我们可以开始精简框架。每增加一层复杂度，其实都是对模型“在这个环节做不到”的假设。当模型变强，这些假设就会过期。&lt;/p&gt;

&lt;p&gt;例如，Opus 4.6 已经可以原生处理长时间的连贯任务，不再需要强行切碎成多个子任务。但我们依然保留了计划者和评估器，因为它们能提供核心的价值提升。&lt;/p&gt;

&lt;h2 id=&quot;结语工具的终极意义&quot;&gt;结语：工具的终极意义&lt;/h2&gt;

&lt;p&gt;随着模型能力的提升，有趣的设计空间并不会因为模型变强而消失，而是向更高阶的自动化迈进。AI 工程师的挑战在于通过不断寻找新的组合，利用 Harness 突破模型本身的能力边界。&lt;/p&gt;

&lt;p&gt;正如我们所看到的，它不是在替你写代码，它是在帮你构建属于你自己的生产力引擎。&lt;/p&gt;</content><author><name>mengtnt</name></author><summary type="html">Harness 设计：构建长时间运行的应用程序开发框架</summary></entry><entry><title type="html">OpenClaw 效率提升实战</title><link href="https://mengtnt.com/2026/03/28/15-03.html" rel="alternate" type="text/html" title="OpenClaw 效率提升实战" /><published>2026-03-28T07:37:12+00:00</published><updated>2026-03-28T07:37:12+00:00</updated><id>https://mengtnt.com/2026/03/28/15-03</id><content type="html" xml:base="https://mengtnt.com/2026/03/28/15-03.html">&lt;h1 id=&quot;告别繁琐我是如何利用-openclaw-打造个人博客发布自动驾驶系统的&quot;&gt;告别繁琐：我是如何利用 OpenClaw 打造个人博客发布“自动驾驶”系统的&lt;/h1&gt;

&lt;p&gt;在数字化写作的今天，博客发布依然是许多创作者的重要阵地。今天，我想深入分享一下我是如何利用 &lt;strong&gt;OpenClaw&lt;/strong&gt; 彻底重构我的博客发布流程，将原本需要一整天的开发调试工作，压缩到寥寥数次对话即可搞定的全过程。&lt;/p&gt;

&lt;h2 id=&quot;一-提出问题那些被浪费在发布上的时间&quot;&gt;一、 提出问题：那些被浪费在“发布”上的时间&lt;/h2&gt;

&lt;p&gt;我刚开始写博客的时候，发布流程可以用“支离破碎”来形容。我比较喜欢 Obsidian 工具来作为我的写作工具，我的博客时基于 GitHub + Jekyll 的静态博客系统，但每次从写作的灵感形成文章，并且到 GitHub 上的正式上线，都要经历一段极其乏味的“手工折磨”：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;元数据（Frontmatter）的格式地狱&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Jekyll 博客需要严格的 YAML 头信息，包括 &lt;code class=&quot;highlighter-rouge&quot;&gt;title&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;date&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;layout&lt;/code&gt; 等。在 Obsidian 里，我更习惯随心所欲地组织内容，这意味着每次发布前，我得手动计算当前的 ISO 时间戳，核对文件名格式，稍有不慎，博客引擎就会因为解析错误而拒绝编译。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;图片附件的搬运工&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这是流程中最耗时的部分。Obsidian 的图片通常保存在专门的附件文件夹中。发布时，我必须：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;手动找到这些图片。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;复制到博客项目的 &lt;code class=&quot;highlighter-rouge&quot;&gt;images&lt;/code&gt; 文件夹。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;在 Markdown 里将相对路径（或是 Wikilinks 格式）手动修改为博客项目的路径（如 &lt;code class=&quot;highlighter-rouge&quot;&gt;/images/my_photo.png&lt;/code&gt;）。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果遇到同名图片，还得小心翼翼地重命名并更新所有引用。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;版本控制的重复劳动&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最后，我得切换到终端，输入那套已经滚瓜烂熟却毫无生趣的命令：&lt;code class=&quot;highlighter-rouge&quot;&gt;git add .&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;git commit -m &quot;update&quot;&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;git push&lt;/code&gt;。如果遇到远程仓库有更新，还得处理冲突。&lt;/p&gt;

&lt;p&gt;之前我都是通过写过 Shell 脚本来做这些枯燥的发布工作，但脚本的维护成本极高：路径配置、异常处理、逻辑扩展……每当我想增加一个小功能时，都得面对那一坨凌乱的代码重新调试。有时候发布一篇文章的心理负担，甚至盖过了写作本身的乐趣。&lt;/p&gt;

&lt;h2 id=&quot;二-重点介绍openclaw--obsidian-的配置全过程&quot;&gt;二、 重点介绍：OpenClaw + Obsidian 的配置全过程&lt;/h2&gt;

&lt;p&gt;这就是本文的核心所在。我整个过程一行 shell 脚本都没写。而是在带娃去游乐场用手机做出来的过程。下面就介绍下如何通过 &lt;strong&gt;OpenClaw&lt;/strong&gt; 的 &lt;strong&gt;Skill-Creator&lt;/strong&gt; 打造了一个名为 &lt;code class=&quot;highlighter-rouge&quot;&gt;blog-publisher&lt;/code&gt; 的智能技能。&lt;/p&gt;

&lt;h3 id=&quot;1-技能构思描述即开发&quot;&gt;1. 技能构思：描述即开发&lt;/h3&gt;

&lt;p&gt;在 OpenClaw 中，我不需要编写繁琐的逻辑判断。我只需要以人类的语言告诉它：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“创建一个叫 blog-publisher 的技能。它需要能自动搜寻 Obsidian 里的文章，处理图片迁移，自动生成 Frontmatter，最后执行 Git 提交并推送到 nas 分支。如果推送失败，要自动执行强制推送。”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这种“描述即开发”的模式，直接跳过了传统编程中的语法纠结，让我能专注于业务逻辑本身。&lt;/p&gt;

&lt;h3 id=&quot;2-核心逻辑配置多技能联动&quot;&gt;2. 核心逻辑配置：多技能联动&lt;/h3&gt;

&lt;p&gt;我的 &lt;code class=&quot;highlighter-rouge&quot;&gt;blog-publisher&lt;/code&gt; 技能并不是孤立存在的，它巧妙地调用了 OpenClaw 已有的生态：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Obsidian 联动&lt;/strong&gt;：利用 &lt;code class=&quot;highlighter-rouge&quot;&gt;obsidian-official-cli&lt;/code&gt; 技能，通过命令行与我的笔记库直接对话，实现毫秒级的文章检索。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;环境韧性&lt;/strong&gt;：我为技能配置了严格的 &lt;code class=&quot;highlighter-rouge&quot;&gt;git pull&lt;/code&gt; 预处理。这意味着无论我在哪台设备上工作，OpenClaw 都能保证我面对的是最新的代码。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;3-细节配置脚本化注入&quot;&gt;3. 细节配置：脚本化注入&lt;/h3&gt;

&lt;p&gt;为了追求极致的稳定性，我让 OpenClaw 为该技能生成了一个 Python 核心脚本 &lt;code class=&quot;highlighter-rouge&quot;&gt;publish.py&lt;/code&gt;。这个脚本专门负责最棘手的图片处理逻辑：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;正则扫描&lt;/strong&gt;：自动识别 Markdown 中的 &lt;code class=&quot;highlighter-rouge&quot;&gt;![]()&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;[[]]&lt;/code&gt; 格式引用。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;冲突检测&lt;/strong&gt;：如果目标 &lt;code class=&quot;highlighter-rouge&quot;&gt;images&lt;/code&gt; 文件夹已存在同名图片，脚本会自动采用 MD5 或计数器方案重命名，并反向更新 Markdown 中的链接。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;路径重写&lt;/strong&gt;：统一将路径前缀修正为 &lt;code class=&quot;highlighter-rouge&quot;&gt;/images/&lt;/code&gt;。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;4-严格规范执行命令的洁癖&quot;&gt;4. 严格规范：执行命令的“洁癖”&lt;/h3&gt;

&lt;p&gt;在配置过程中，我利用 OpenClaw 对技能进行了微调，强制要求所有的 Git 提交必须经过我的审查确认，这种严格的规范确保了博客仓库提交记录的整洁，避免了 AI 生成随机提交信息的混乱。&lt;/p&gt;

&lt;h2 id=&quot;三-使用感受效率的降维打击&quot;&gt;三、 使用感受：效率的“降维打击”&lt;/h2&gt;

&lt;p&gt;回顾今天使用 OpenClaw 的整个过程，最让我震撼的是其带来的生产力释放。&lt;/p&gt;

&lt;h3 id=&quot;1-从天到分钟的飞跃&quot;&gt;1. 从“天”到“分钟”的飞跃&lt;/h3&gt;

&lt;p&gt;以往如果要从零开发这样一套完整的、具备健壮图片处理和异常重试逻辑的发布系统，即使是资深开发者，从编写核心代码、处理各种特殊字符路径的 Corner Case，到最终在服务器上稳定运行，起码需要投入一整天的时间和精力。&lt;/p&gt;

&lt;p&gt;但在 OpenClaw 中，我仅仅花费了不到一百个提示词（Prompt）进行沟通和微调，它就完美地生成了文档、脚本并完成了打包。这种效率提升不是百分之几，而是量级（Order of Magnitude）的跨越。&lt;/p&gt;

&lt;h3 id=&quot;2-真人案例游乐场里的自动驾驶发布&quot;&gt;2. 真人案例：游乐场里的“自动驾驶”发布&lt;/h3&gt;

&lt;p&gt;最不可思议的场景发生在今天下午：&lt;strong&gt;我正带着孩子在游乐场玩耍，坐在喧闹的长椅上休息。就在这时，我通过手机端的 Telegram 客户端，像聊天一样给 OpenClaw 发送了指令：“把 OpenClaw 效率提升实战 这篇文章发布。”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;几秒钟后，OpenClaw 向我反馈：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;✅ Obsidian 同步完成。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;✅ 找到文章并生成 Frontmatter。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;✅ 自动处理了文中的 Mermaid 图表。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;✅ 脚本成功将更改推送到 nas 分支。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;整个过程，我手机的亮屏时间不到一分钟，而背后却完成了一系列复杂的开发运维动作。文章现在已静静地躺在我的 GitHub 博客仓库中，等待着全球读者的访问。&lt;/p&gt;

&lt;h2 id=&quot;四-结语工具的终极意义&quot;&gt;四、 结语：工具的终极意义&lt;/h2&gt;

&lt;p&gt;OpenClaw 给我带来的不仅仅是时间上的节省，更是一种“掌控感”。它消除了技术琐碎带来的焦虑，让我能重新回到创作的本质。如果你也觉得自己的工作流中有太多繁琐的手工环节，不妨尝试把它们交给 OpenClaw。它不是在替你写代码，它是在帮你构建属于你自己的生产力引擎。&lt;/p&gt;</content><author><name>mengtnt</name></author><summary type="html">告别繁琐：我是如何利用 OpenClaw 打造个人博客发布“自动驾驶”系统的</summary></entry><entry><title type="html">如何用 Metal 做视频帧合流渲染</title><link href="https://mengtnt.com/2024/10/01/metal-render.html" rel="alternate" type="text/html" title="如何用 Metal 做视频帧合流渲染" /><published>2024-10-01T02:00:19+00:00</published><updated>2024-10-01T02:00:19+00:00</updated><id>https://mengtnt.com/2024/10/01/metal-render</id><content type="html" xml:base="https://mengtnt.com/2024/10/01/metal-render.html">&lt;h1 id=&quot;背景&quot;&gt;背景&lt;/h1&gt;

&lt;p&gt;最近在做远程控制的功能，使用的技术方案是远程控制的鼠标数据流和桌面画面视频流分开传输。这样观看端就需要把鼠标的画面和桌面的画面进行合并渲染。如果在数据层面做利用 FFmpeg 框架就可以实现两路视频流的合并。这个可能在直播场景比较适合，由于视频会议对实时性要就比较高，用户在远程控制时，延时忍受度较低，所以为了体验就采用了客户端端在渲染时，来合并两路流进行显示。&lt;/p&gt;

&lt;p&gt;这里就引申到本文讨论的话题了，在 iOS 平台用 Metal 如何来渲染两路视频流到一个视图上？&lt;/p&gt;

&lt;h1 id=&quot;1-metal-是如何渲染图形的&quot;&gt;1. Metal 是如何渲染图形的&lt;/h1&gt;

&lt;p&gt;首先我们先看下图形如何通过GPU显示出来的。下面是一个简单的示意图。
&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/09/93b28736-b6e0-4e9d-9c17-a252c07d582c.png&quot; alt=&quot;Description&quot; style=&quot;width:640px; height:600px;&quot; /&gt;
渲染过程通过  CPU 给 GPU 发送相应的渲染指令，然后把数据拷贝到 GPU 中，用 GPU 渲染上屏。从上图的过程可以看出，图形渲染的过程 CPU 负责组装指令，而 GPU 负责上屏渲染这个过程。
那Metal 其实就是封装了 GPU 图形化的接口，方便开发者调用 GPU 的能力。整个渲染过程和大多数图形框架 OpenGL Vulkan 基本相似，我们来看下这个流程。&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;代码上配置的 GPU 指令，这个配置过程是在 CPU 中执行的，有兴趣可以看下&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide&quot;&gt;官方文档&lt;/a&gt;。下图是 Metal 框架中可以配置的命令的种类。
&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/09/ffb7030f-d848-4fd4-b8f3-040349563bc2.png&quot; alt=&quot;&quot; /&gt;
上图中位块命令编码器（Blit Command Encode）是用于处理数据传输和图像处理的操作。它允许开发者高效地在 GPU 上执行内存拷贝、图像缩放、图像转换等任务。 计算命令编码器(Compute Command Encoder)用于编码在 GPU 上执行计算内核的命令。可以执行通用计算，例如图像处理、机器学习或任何并行化的任务。而我们做图形渲染主要关心的是渲染编码器(Render Command Encoder)如何使用的，渲染编码器需要配置的有顶点坐标数据、纹理数据、采样信息、深度信息然后发送到 GPU中，从此图中也可以看出来，除了图形处理外，同时 Metal 还可以做机器学习相关的计算。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;下面看下渲染指令的配置过程，下图就描述了一个渲染指令的创建过程
&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/09/9861328c-bdee-41f3-bddf-56194d9a2b46.png&quot; alt=&quot;Description&quot; style=&quot;width:600px; height:600px;&quot; /&gt;
其中可以看到 Vertex Function 和 Fragment Function 就是 GPU 可以被编程的核心，通过注入顶点和片段着色器就可以利用 GPU 自定义渲染的逻辑。Metal 会把这些指令编译优化后，配置到 GPU中。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;最后就开始执行 Metal 的渲染 ，下图就是渲染出最终图形这个过程中 GPU 需要执行的各个阶段，也就是我们常说的渲染管道 PipLine。
&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/09/bb113629-6b01-4240-a42c-833d4f5ce74e.png&quot; alt=&quot;&quot; /&gt;
渲染管道的流程就是从上面我们配置的数据和自定义的渲染逻辑，然后计算出相应位置的像素点位信息，经过深度和模版测试(本质上就是对深度信息、透明度、以及叠加视图进行裁剪和融合操作)，渲染到缓冲区，最终根据屏幕的刷新率渲染上屏。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;上述就是 Metal 渲染 Pipline 的基本描述，有兴趣可以下载&lt;a href=&quot;https://developer.apple.com/documentation/metal/using_a_render_pipeline_to_render_primitives?language=objc&quot;&gt;官方示例代码&lt;/a&gt;。从上面的配置过程可以看出，开发 Metal 渲染要做的两件事情，在 CPU 中配置渲染的 GPU 指令，通过框架提供的可编程PipLine，编写自定义的 Shader 给 GPU，执行定制化的渲染。&lt;/p&gt;

&lt;h1 id=&quot;2-webrtc-的渲染架构&quot;&gt;2. WebRTC 的渲染架构&lt;/h1&gt;

&lt;p&gt;在我们开始写视频合流的逻辑时，先来看下我们音视频会议用的 WebRTC 框架，是如何使用 Metal 来渲染的？下图是 WebRTC Metal 渲染的类图。
&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/09/8cb4e6e6-f80f-46b2-a8e7-55b3a635679e.png&quot; alt=&quot;&quot; /&gt;
从上图看还是比较简单的，获取解码后的视频数据帧，传递给 RTCMTLVideoView 后，RTCMTLVideoView 通过 DisplayLink 定时器指定渲染帧率，然后根据视频帧的不同颜色格式，分发给不同的 Render 渲染器执行渲染。&lt;/p&gt;

&lt;p&gt;然后我们鼠标合流的过程可以用下面的流程图描述下。
&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/09/66eb0c46-227a-485f-8d67-2369ea2a5c0b.png&quot; alt=&quot;Description&quot; style=&quot;width:400px; height:600px;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;流程图中鼠标渲染的逻辑也很简单，在渲染视频帧的时候，发现相应时间点有鼠标帧过来时，就通过 Metal 合流的方式渲染上屏。下面我们就详细来看下 Metal 渲染上屏的代码。&lt;/p&gt;

&lt;h1 id=&quot;3-metal-渲染示例代码&quot;&gt;3. Metal 渲染示例代码&lt;/h1&gt;

&lt;h2 id=&quot;31-渲染管道配置&quot;&gt;3.1 渲染管道配置&lt;/h2&gt;
&lt;p&gt;开发 Metal 渲染的代码，先看下 CPU 如何配置渲染管道这个过程，下面是一个示例代码。&lt;/p&gt;
&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonnull&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instancetype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;initWithMetalKitView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonnull&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MTKView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mtkView&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;super&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_device&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mtkView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;NSURL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imageFileLocation&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSBundle&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mainBundle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;URLForResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;background&quot;&lt;/span&gt;
                                                           &lt;span class=&quot;nl&quot;&gt;withExtension:&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tga&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        
        &lt;span class=&quot;n&quot;&gt;_texture&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loadTextureUsingAAPLImage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;imageFileLocation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Set up a simple MTLBuffer with vertices which include texture coordinates&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AAPLVertex&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quadVertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Pixel positions, Texture coordinates&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;

            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Create a vertex buffer, and initialize it with the quadVertices array&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_vertices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_device&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newBufferWithBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quadVertices&lt;/span&gt;
                                         &lt;span class=&quot;nl&quot;&gt;length:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quadVertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                                        &lt;span class=&quot;nl&quot;&gt;options:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLResourceStorageModeShared&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Calculate the number of vertices by dividing the byte length by the size of each vertex&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_numVertices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;/// Create the render pipeline.&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Load the shaders from the default library&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLLibrary&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defaultLibrary&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_device&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newDefaultLibrary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vertexFunction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;defaultLibrary&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newFunctionWithName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;vertexShader&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fragmentFunction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;defaultLibrary&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newFunctionWithName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;samplingShader&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Set up a descriptor for creating a pipeline state object&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MTLRenderPipelineDescriptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pipelineStateDescriptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLRenderPipelineDescriptor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;pipelineStateDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Texturing Pipeline&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;pipelineStateDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vertexFunction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vertexFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;pipelineStateDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fragmentFunction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fragmentFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;pipelineStateDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;colorAttachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelFormat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mtkView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;colorPixelFormat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;NSError&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_pipelineState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_device&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newRenderPipelineStateWithDescriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pipelineStateDescriptor&lt;/span&gt;
                                                                 &lt;span class=&quot;nl&quot;&gt;error:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;NSAssert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_pipelineState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to create pipeline state: %@&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;_commandQueue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_device&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newCommandQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;通过上面的代码可以看出，主要是在配置顶点向量和着色器函数。我们画如下的图来看下顶点坐标是如何定义的。
&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/09/7027766f-c806-4579-8c28-1a28bc071f41.png&quot; alt=&quot;Description&quot; style=&quot;width:400px; height:400px;&quot; /&gt;
可以看出来中心点是 (0,0) 。因为我们视频帧只用了 2D 坐标，如果 3D 坐标的话，坐标系如下。
&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/09/20022f4f-759f-4fc3-af2b-7758a2ad885f.png&quot; alt=&quot;Description&quot; style=&quot;width:400px; height:400px;&quot; /&gt;
Metal 渲染使用的是左手坐标系，如果是OpenGL右手坐标系，Z 轴就是反向的，个人感觉左手更符合人的直觉。&lt;/p&gt;

&lt;p&gt;这里我们要注意的配置渲染顶点坐标的类型，用的 MTLPrimitiveTypeTriangle。MTLPrimitiveTypeTriangleStrip  和  MTLPrimitiveTypeTriangle  是 Metal 框架中用于指定图形渲染顶点数据类型的枚举值。它们之间的主要区别在于如何处理顶点数据以形成三角形。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;MTLPrimitiveTypeTriangle 这种类型表示独立的三角形。在绘制时，每三个顶点组成一个三角形。因此如果你有 N 个三角形，你需要提供 3 * N 个顶点。 每个三角形的顶点之间没有共享，所有的三角形都是独立的。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;MTLPrimitiveTypeTriangle，这种类型表示对三角形顶点做适当裁剪。在绘制时，第一个三角形由前两个顶点和第三个顶点组成，之后的每个新顶点都会与前两个顶点一起形成一个新的三角形。 这样绘制 N 个三角形只需要 N + 2 个顶点，效率更高，因为可以减少顶点数据的传输。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MTLPrimitiveTypeTriangle 用于独立三角形，而  MTLPrimitiveTypeTriangleStrip  用于通过共享顶点来高效地绘制一系列相连的三角形。选择哪种类型取决于你的具体需求和数据结构。下面的这个顶点坐标类型就是完整的三角形模式，后面可以看到我们实际项目中用的是 MTLPrimitiveTypeTriangleStrip ，因为视频帧是 2D 平面比较简单，可以用4个顶点代表一个正方形。&lt;/p&gt;

&lt;h2 id=&quot;32-渲染命令提交&quot;&gt;3.2 渲染命令提交&lt;/h2&gt;
&lt;p&gt;提交渲染命令给 GPU 的的过程中。看下面的示例代码。&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drawInMTKView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonnull&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MTKView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Create a new command buffer for each render pass to the current drawable&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLCommandBuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commandBuffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_commandQueue&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commandBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;commandBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MyCommand&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Obtain a renderPassDescriptor generated from the view's drawable textures&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;MTLRenderPassDescriptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;renderPassDescriptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentRenderPassDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;renderPassDescriptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_viewportSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bounds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_viewportSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bounds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLRenderCommandEncoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;renderEncoder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commandBuffer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;renderCommandEncoderWithDescriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;renderPassDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;renderEncoder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MyRenderEncoder&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Set the region of the drawable to draw into.&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;renderEncoder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setViewport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLViewport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_viewportSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_viewportSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}];&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;renderEncoder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setRenderPipelineState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_pipelineState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;renderEncoder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setVertexBuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_vertices&lt;/span&gt;
                                &lt;span class=&quot;nl&quot;&gt;offset:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
                              &lt;span class=&quot;nl&quot;&gt;atIndex:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;renderEncoder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setVertexBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_viewportSize&lt;/span&gt;
                               &lt;span class=&quot;nl&quot;&gt;length:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_viewportSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                              &lt;span class=&quot;nl&quot;&gt;atIndex:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AAPLVertexInputIndexViewportSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Set the texture object.  The AAPLTextureIndexBaseColor enum value corresponds&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;///  to the 'colorMap' argument in the 'samplingShader' function because its&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//   texture attribute qualifier also uses AAPLTextureIndexBaseColor for its index.&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;renderEncoder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setFragmentTexture&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_texture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Draw the triangles.&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;renderEncoder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;drawPrimitives&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLPrimitiveTypeTriangle&lt;/span&gt;
                          &lt;span class=&quot;nl&quot;&gt;vertexStart:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
                          &lt;span class=&quot;nl&quot;&gt;vertexCount:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;renderEncoder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;endEncoding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Schedule a present once the framebuffer is complete using the current drawable&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commandBuffer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;presentDrawable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentDrawable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Finalize rendering here &amp;amp; push the command buffer to the GPU&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commandBuffer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:_numVertices];&lt;/code&gt;这里就可以看到我们提交给 GPU 的顶点向量的类型为 MTLPrimitiveTypeTriangle ，所以正方形的话需要2个三角形来表示。这里还有个注意点，我们顶点向量传递的是实际像素的话，需要额外传递下 _viewportSize ，因为我们之后片段着色器用到的顶点位置其实都是相对位置，不是实际的像素位置，主要是为了方便纹理贴图时，计算纹理位置。之后我们也会讲到。&lt;/p&gt;

&lt;h2 id=&quot;33-数据传递&quot;&gt;3.3 数据传递&lt;/h2&gt;
&lt;p&gt;内存数据传输，例如 &lt;code class=&quot;highlighter-rouge&quot;&gt;[texture replaceRegion:region mipmapLevel:0 withBytes:image.data.bytes bytesPerRow:bytesPerRow];&lt;/code&gt; 给片段着色器传递数据，本质上就是 CPU 把内存的数据给 GPU 的内存。这里要注意颜色格式问题，一定要约定好像素的格式，MTLPixelFormatBGRA8Unorm 表示的32位的纹理数据。不然数据拷贝的时候会因为格式问题导致拷贝的数据 GPU 无法处理。&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLTexture&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loadTextureUsingAAPLImage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSURL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;AAPLImage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AAPLImage&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initWithTGAFileAtLocation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSAssert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to create the image from %@&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;absoluteString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;MTLTextureDescriptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textureDescriptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLTextureDescriptor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Indicate that each pixel has a blue, green, red, and alpha channel, where each channel is&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// an 8-bit unsigned normalized value (i.e. 0 maps to 0.0 and 255 maps to 1.0)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;textureDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelFormat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MTLPixelFormatBGRA8Unorm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// Set the pixel dimensions of the texture&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;textureDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;textureDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// Create the texture from the device by using the descriptor&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLTexture&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texture&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_device&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newTextureWithDescriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textureDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// Calculate the number of bytes per row in the image.&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSUInteger&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytesPerRow&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;MTLRegion&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;region&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;                   &lt;span class=&quot;c1&quot;&gt;// MTLOrigin&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// MTLSize&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// Copy the bytes from the data object into the texture&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texture&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;replaceRegion&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;region&lt;/span&gt;
                &lt;span class=&quot;nl&quot;&gt;mipmapLevel:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
                  &lt;span class=&quot;nl&quot;&gt;withBytes:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;
                &lt;span class=&quot;nl&quot;&gt;bytesPerRow:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytesPerRow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;上面是纹理数据配置的代码，我们讲下纹理坐标位置，纹理本质就是贴图，因为图片都是2D的，所以一般都是用 float2 向量定义的。float2 这种变量是 SIMD （单指令多数据结构）代表的就是2维的平面向量(x,y)。下图就是纹理坐标的定义。
&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/09/bb468dac-1a0d-4d92-afdc-50fe7e90f247.png&quot; alt=&quot;Description&quot; style=&quot;width:600px; height:400px;&quot; /&gt;
纹理坐标都是用相对位置计算的，这样着色时方便和顶点向量位置进行计算，尤其涉及到缩放和位移时，相对位置的优势体现出来。&lt;/p&gt;

&lt;h2 id=&quot;34-着色器的编写&quot;&gt;3.4 着色器的编写&lt;/h2&gt;
&lt;p&gt;下面就是顶点着色器和片段着色器代码编写了。&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;我们就可以看下顶点着色器如何工作了，下面是顶点着色器代码。&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;float2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;float2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Vertex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;float4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]];&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;float2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RasterizerData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

 &lt;span class=&quot;n&quot;&gt;vertex&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RasterizerData&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;vertexPassthrough&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Vertex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;verticies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]],&lt;/span&gt;
                                   &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vertex_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;RasterizerData&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Vertex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;verticies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;// Get the viewport size and cast to float.&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;float2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewportSize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;viewportSizePointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

       &lt;span class=&quot;c1&quot;&gt;// To convert from positions in pixel space to positions in clip-space,&lt;/span&gt;
       &lt;span class=&quot;c1&quot;&gt;//  divide the pixel coordinates by half the size of the viewport.&lt;/span&gt;
       &lt;span class=&quot;c1&quot;&gt;// Z is set to 0.0 and w to 1.0 because this is 2D sample.&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;position&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vector_float4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pixelSpacePosition&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;viewportSize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texcoord&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;先来看几个概念，&lt;code class=&quot;highlighter-rouge&quot;&gt;float4 position [[position]];&lt;/code&gt; 这个是属性限定的语法结构，顾名思义就是这个属性限定它的使用方式。position 代表就是从顶点向量中获取到的裁剪空间位置，用来输出裁剪空间的位置信息给片段着色器。如何理解裁剪空间位置，裁剪的过程其实就是把我们定义好的顶点向量构成三角形，然后根据传递的视图实际像素大小，进行像素插值裁剪，然后计算出相应的位置。例如下面这个顶点着色器函数 vertexPassthrough ，其中定义的变换输出的结构 Varyings out; 这个变量的值赋值是通过 &lt;code class=&quot;highlighter-rouge&quot;&gt;const device Vertex &amp;amp;v = verticies[vid];&lt;/code&gt; 而这里面的 vid 本质上就是实际屏幕的像素位置，比如你的屏幕是320 px，采样的时候就会从 0 到 320 计算三角形实际的位置，然后赋值给 out。这里也可以看到实际像素都做了相对位置的变换，方便之后使用。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;顶点着色器的输出，就是片段着色器的输入。VertexIn in [[stage_in]]; 表示该变量是顶点着色器的输出，传入已经插值裁剪好的像素点位，给片段着色器使用，以便于给每个点位分配颜色值。&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;fragment&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float4&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;samplingShader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RasterizerData&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stage_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt;
               &lt;span class=&quot;n&quot;&gt;texture2d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;half&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colorTexture&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sampler&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;textureSampler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mag_filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                      &lt;span class=&quot;n&quot;&gt;min_filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Sample the texture to obtain a color&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;half4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colorSample&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colorTexture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textureSampler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textureCoordinate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;float4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;colorSample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// return the color of the texture&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;上面 texture2d&lt;half&gt; colorTexture [[ texture(0) ]] 这个属性限定符的作用，从我们在管道中配置的片段着色器数据的索引，例如：``` [renderEncoder setFragmentBuffer:_kernelSizeBuffer offset:0 atIndex:0];  ``` 这个就是获取我们这个配置的纹理数据。然后读取纹理中颜色值渲染到相应的顶点坐标位置。至此整个渲染过程就完成了。&lt;/half&gt;&lt;/p&gt;

&lt;h1 id=&quot;4-视频流混合的过程&quot;&gt;4. 视频流混合的过程&lt;/h1&gt;

&lt;p&gt;渲染的基本流程梳理完成后，那我们看下鼠标纹理数据如何渲染到视频上的，其中核心就是设置鼠标的顶点坐标和纹理数据，以及在 Shader 中融合鼠标纹理和视图画面的纹理。我们先看下 WebRTC 视频帧顶点向量的定义。&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coordX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coordY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cropLeft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cropBottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;coordX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coordY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cropRight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cropBottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coordX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;coordY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cropLeft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cropTop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;coordX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;coordY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cropRight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cropTop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其中 coordX ，coordY 这些都是裁剪过的视频帧的坐标位置，在 WebRTC 中这些顶点向量都是用相对位置来表示。所以我们在设置鼠标的顶点位置时，也用相对位置如下：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BOOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setupTexturesForMouseFrame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonnull&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RTCMouseCursorFrame&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;incomingFrame&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;MTLTextureDescriptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textureDescriptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLTextureDescriptor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;textureDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textureType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MTLTextureType2D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;textureDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;textureDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;textureDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelFormat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MTLPixelFormatBGRA8Unorm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;textureDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;usage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MTLTextureUsageShaderRead&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_cursorTexture&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_device&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newTextureWithDescriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_cursorTexture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;RTCLogError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;[RND]MTLRender:%p Failed to create cursor texture&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 纹理数据&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_cursorTexture&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;replaceRegion&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MTLRegionMake2D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                      &lt;span class=&quot;nl&quot;&gt;mipmapLevel:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
                        &lt;span class=&quot;nl&quot;&gt;withBytes:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mouseFrameBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rgbaData&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
                      &lt;span class=&quot;nl&quot;&gt;bytesPerRow:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mouseFrameBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stride&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// 顶点数据的相对位置&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;memcpy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_cursorBlendRectBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;YES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后下面就是融合的片段着色器。&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;fragment&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;half4&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fragmentColorBlend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Varyings&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stage_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texture2d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;textureY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]],&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;texture2d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;textureCbCr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]],&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;texture2d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;textureBlend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]],&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;constant&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float4&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]],&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (left, top, right, bottom)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;constant&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;enableBlend&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sampler&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clamp_to_edge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sampler&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendSampler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clamp_to_edge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                             &lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;float2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;textureY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;uv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;textureCbCr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;float4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;video&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;403&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;344&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;714&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;770&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;float4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;enableBlend&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;factorW&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;factorH&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;float2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendTextureCoordinate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;factorW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;factorH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;float4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;textureBlend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blendSampler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendTextureCoordinate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;video&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rgb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rgb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blendResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;video&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;half4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;融合的方法很简单。这里我们用了个取巧的方式，直接把鼠标的顶点相对位置存储到了片段着色器的 buffer  中。然后在片段着色器执行时，从 buffer 读取鼠标上下左右的顶点 blendRect，只要判断鼠标的顶点落入到了 blendRect 区域中。就从鼠标纹理中 textureBlend 获取相应的颜色值，通过向量运算返回融合的纹理数据。这里需要注意是纹理采样的数据都是2维的向量。但是片段着色器输出的是4维的向量分别表示 (r,g,b,a)，这个过程中要注意向量的定义，避免赋值造成的错误。&lt;/p&gt;

&lt;h1 id=&quot;5-总结&quot;&gt;5. 总结&lt;/h1&gt;

&lt;p&gt;通过上面的分享基本可以了解用 Metal 如何把画面渲染上屏了。可以看到正是 Metal 框架的封装，才让我们很容易的利用 GPU 的资源渲染画面。虽然视频处理 Metal 渲染往往是 2D，但是了解其工作原理，对于我们做 3D 方面的渲染还是很有借鉴意义。当然如果要做 3D 渲染工作，其实还有很多复杂的工作要做，例如配置顶点向量的法线，光照以及纹理材质的深度的融合等等。这个过程往往会交给 3D 引擎来做，建模师只需要把模型配置好，加载模型后引擎来解析这些数据配置给 GPU。&lt;/p&gt;</content><author><name>mengtnt</name></author><summary type="html">背景</summary></entry><entry><title type="html">VisionPro所需的SwiftUI</title><link href="https://mengtnt.com/2024/03/02/vision-swiftui.html" rel="alternate" type="text/html" title="VisionPro所需的SwiftUI" /><published>2024-03-02T02:00:19+00:00</published><updated>2024-03-02T02:00:19+00:00</updated><id>https://mengtnt.com/2024/03/02/vision-swiftui</id><content type="html" xml:base="https://mengtnt.com/2024/03/02/vision-swiftui.html">&lt;h2 id=&quot;1-背景&quot;&gt;1. 背景&lt;/h2&gt;
&lt;p&gt;在我们开发 Vision Pro 开发之前，先大致了解下 SwiftUI 框架是非常有必要的，不然很多系统的新特性苹果往往只会提供 SwiftUI 的演示代码，甚至有些 API 只能 SwiftUI 才能使用。所以想要更好的适配 Vision Pro 最好的方式是用 SwiftUI 进行开发。&lt;/p&gt;

&lt;p&gt;SwitUI 几个特点相比大家都有所耳闻：声明式编程、数据驱动、自适应布局。我们来看下数据驱动的 UI 框架一个经典的公式。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/02/6fa0034e-2f2a-427f-a5c8-bfe4cb7a0b64.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这个公式就一目了然了，UI 的展示就是数据状态的一个函数映射，所有的数据驱动的 UI 框架都遵循这个原则。那么 SwiftUI 的框架又是什么样子的呢？下面针对 SwiftUI 几个重要特征的由来做个介绍，限于篇幅详情的学习可以参看 &lt;a href=&quot;[https://](https://developer.apple.com/tutorials/swiftui)&quot;&gt;官方文档&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;2-dsl语法&quot;&gt;2. DSL语法&lt;/h2&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ContentView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;systemName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;shareplay&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;foregroundColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于熟悉 Swift 语言，但是初次接触 SwiftUI 的同学来说，上面的语法大概率会有下面的疑惑：“为啥 body 这个函数没有 return 任何变量？” 假如说我们把上面的这段代码，改为下面的形式，相信做过 iOS 开发的同学一眼都能看明白。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ContentView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;textView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;imageView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;systemName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;shareplay&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;foregroundColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;container&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addSubView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;textView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addSubView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imageView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;container&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其实这种语法就是 SwifUI 描述视图布局的领域专用语言 (DSL)，在编译时就会把这种 DSL 转换成我们上面可以看懂的视图布局方式。DSL 的好处就是简化语法，只需要描述问题即可不用提供问题的解决过程，然后框架层会自动生成解决问题的繁琐代码。这看起来其实有点类似数据库的 SQL 语句，只要描述下我要解决的问题即可，大大节省了代码量，让代码可读性也变的更好。&lt;/p&gt;

&lt;p&gt;那么就有下个疑问了，SwiftUI 这种 DSL 语法是如何构建的呢？答案是通过 ViewBuilder 构建出来的。要了解 ViewBuilder 这里我们就需要了解 Swift 语言标记语法了，何为标记语法？说白了就是可以帮我们在语法树中插入一段我们自定义的代码，方便我们简化代码的结构，我简单的用下面的图示表示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/02/281f8d1d-d21b-4fb6-aebe-45ff0a8949bd.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;其中图 1 就代表正常的语法树，而图 2 就代表我们自定义的语法树，标记语法就相当于给了我们一个能力，把语法树上的一个节点替换成我们自定义的语法树的内容。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// The type of view representing the body of this view.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;///&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// When you create a custom view, Swift infers this type from your&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// implementation of the required ``View/body-swift.property`` property.&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;associatedtype&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// The content and behavior of the view.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;///&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// When you implement a custom view, you must implement a computed&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// `body` property to provide the content for your view. Return a view&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// that's composed of built-in views that SwiftUI provides, plus other&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// composite views that you've already defined:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;///&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;///     struct MyView: View {&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;///         var body: some View {&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;///             Text(&quot;Hello, World!&quot;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;///         }&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;///     }&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;///&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// For more information about composing views and a view hierarchy,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// see &amp;lt;doc:Declaring-a-Custom-View&amp;gt;.&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@ViewBuilder&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@MainActor&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面这段代码就是 body 的声明，可以看出来这个函数传递的参数是一个 ViewBuilder 。然后 SwiftUI 通过 ViewBuilder 的能力重新构造语法树，从而把添加视图的过程给实现了。ViewBuilder 是通过 Swift 语言中 ResultBuilder 这个标记语法能力构建的，ResultBuilder 是 Swift 语言提供给开发者构建自定义 DSL 的能力。而 ResultBuilder 这个语法又是从 FunctionBuilder 延伸出来的，他们的演化历史简单的表述如下：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果你想要详细了解苹果标记语法的能力，建议你观看 &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10253/&quot;&gt;apple result builder&lt;/a&gt; 这个 WWDC 的视频会有详细的讲解，这里不再赘述。&lt;/p&gt;

&lt;p&gt;通过上面的分析过程，我们其实可以归纳出，支持高阶函数的语言，本质上构建这种语法应该都不困难。在前端开发看来，SwiftUI 这种 DSL 是他们很早就具备的能力，例如 React 和 VUE 都有自己定义的 DSL。并且像 google 的跨平台框架 Flutter 也是用的声明式的标记语法实现的，还有最近华为的纯血鸿蒙系统，前端 UI 构建也采用了声明式的框架。可见声明式的 UI 框架还是非常受欢迎的。&lt;/p&gt;

&lt;h2 id=&quot;3-视图树&quot;&gt;3. 视图树&lt;/h2&gt;

&lt;p&gt;下面我们就来看下 SwiftUI 的视图树是如何通过上述的 DSL 构建的。&lt;/p&gt;

&lt;h3 id=&quot;31-链式语法&quot;&gt;3.1 链式语法&lt;/h3&gt;
&lt;p&gt;我们来看一个简单的例子如下：&lt;/p&gt;
&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backgound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;我们再来看下这段代码，我们把链式调用的位置交换了下，代码如下：&lt;/p&gt;
&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;来看下两段代码运行的结果：左一就是第一段代码生成的视图，右一是第二段代码的视图。
&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/02/86f27896-c29e-4cd1-863f-ba02bf541291.png&quot; alt=&quot;Image Description&quot; width=&quot;600&quot; height=&quot;auto&quot; /&gt;&lt;/p&gt;

&lt;p&gt;为什么会不同呢？分析这个视图的构建过程，SwiftUI 的视图构建是一个递归的过程，根据视图树上面所有子视图的尺寸决定视图的大小，再根据链式调用自下而上的构建相应的视图，每个链式操作其实都会创建一个子视图，第一段代码的构建过程如下：
&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/02/0029e12d-de6a-4362-9482-b052fd5c7bb7.png&quot; alt=&quot;Image Description&quot; width=&quot;400&quot; height=&quot;auto&quot; /&gt;
可以看出来确定完视图大小后，会先放置 &lt;code class=&quot;highlighter-rouge&quot;&gt;.backgound&lt;/code&gt; 这个子视图，然后再放置它的子视图 &lt;code class=&quot;highlighter-rouge&quot;&gt;.padding&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;.color&lt;/code&gt;，最后的 &lt;code class=&quot;highlighter-rouge&quot;&gt;.text&lt;/code&gt; 是属于 &lt;code class=&quot;highlighter-rouge&quot;&gt;.padding&lt;/code&gt; 的子视图，所以自然背景色范围就比较广。下图就是第二段代码的构建过程。
&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/02/8f04a6ec-a385-4e14-8fad-f8146e0e19c4.png&quot; alt=&quot;Image Description&quot; width=&quot;400&quot; height=&quot;auto&quot; /&gt;
可以看出来是先构建了 &lt;code class=&quot;highlighter-rouge&quot;&gt;.padding&lt;/code&gt; 这个子视图，然后再放置 &lt;code class=&quot;highlighter-rouge&quot;&gt;.backgound&lt;/code&gt; 子视图，自然背景就被裁剪掉了。
&lt;strong&gt;每个链式调用本质上就是创建一个子视图，然后自下而上构建视图的&lt;/strong&gt; ，记住这个构建规则，对于你写 SwiftUI 的代码会有很大的帮助。那我们可以自定义链式语法的函数么？这就涉及到另一个概念「修饰器」，下面我们来看下。&lt;/p&gt;

&lt;h3 id=&quot;32-修饰器-viewmodifer&quot;&gt;3.2 修饰器 (ViewModifer)&lt;/h3&gt;

&lt;p&gt;修饰器相当于给视图增加了扩展能力。下面就是扩展视图背景的一个例子。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BGType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clear&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;glass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;meterial&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pureColor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dtBackgroundEffect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;effectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BGType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clear&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;modifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;dtBackgroundEffectModifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;effectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;effectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;dtBackgroundEffectModifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ViewModifier&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;effectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BGType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clear&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;effectType&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;glass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;glassBackgroundEffect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;displayMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;always&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;meterial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;regularMaterial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pureColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIColor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dt_color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;withHexString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;29231F&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这段代码的 switch case 语法给修饰器创建了不同的视图，这样调用的地方就只要一行代码 &lt;code class=&quot;highlighter-rouge&quot;&gt;Text(&quot;name&quot;).dtBackgroundEffect(effectType:effect)&lt;/code&gt; 就可以了，避免了写大量这种重复的代码，对于代码复用这种方式非常有用。不过看到这里你可能会有疑问， 扩展视图能力类似下面这样的写法不是也可以么？&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dtBackgroundEffect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;effectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BGType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clear&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;effectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BGType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clear&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;effectType&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;glass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;glassBackgroundEffect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;displayMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;always&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;meterial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;regularMaterial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pureColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIColor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dt_color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;withHexString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;29231F&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这段代码初看没啥问题，但是编译的话就会报错 &lt;code class=&quot;highlighter-rouge&quot;&gt;Function declares an opaque return type 'some View', but the return statements in its body do not have matching underlying types&lt;/code&gt; 。从编译报错里面可以明确看出，虽然返回的都是 &lt;code class=&quot;highlighter-rouge&quot;&gt;some View&lt;/code&gt; 但是 Swift 语言对类型检测非常严格的，我们来看下 Background 定义。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;@inlinable&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ignoresSafeAreaEdges&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Edge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ShapeStyle&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里返回的 some view 类型会有一个传递的模版参数 S 的定义，如果模版类型不一致的话 Swift 就会认为他们类型本质上不一样的。如何来解决这个问题，其实我们只要增加 ViewBuilder 标记语法，用如下的方式就可以编译通过了。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;@ViewBuilder&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dtBackgroundEffect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;effectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BGType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clear&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;effectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BGType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clear&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;effectType&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;glass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;glassBackgroundEffect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;displayMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;always&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;meterial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;regularMaterial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pureColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIColor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dt_color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;withHexString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;29231F&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里就可以看出来 ViewBuilder 其实会重新组装我们的 View 成为一个确定的类型，就不会报错了。那么问题来了，ViewModifer 和用普通的 Extension Function 来扩展有什么大的区别呢？这里简单描述下用法的区别。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;如果你代码重用的部分比较复杂，涉及到很多条件创建不同的 View，这时候就适合 ViewModifer ，因为在性能方面，ViewModifier它可以利用 SwiftUI 的优化机制，避免不必要的视图层次重建。而我们自定义的扩展就没这个能力了。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果你定义的方法只用在特定的一个 View 上，不需要大量的重用，并且扩展相对比较简单，其实就没必要使用 ViewModifer。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;像上面这种需要用 switch case 来创建各种不同的视图来修饰的话，就比较适合用 ViewModifer 了，视图树的构建方式讲到这里，那么数据如何驱动这些视图渲染呢？下面就开始分析下 SwiftUI 的状态管理。&lt;/p&gt;

&lt;h2 id=&quot;4-状态管理&quot;&gt;4. 状态管理&lt;/h2&gt;

&lt;p&gt;我们先来看下面这段代码，SwiftUI 是如何映射数据状态到视图树上面的?&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;@state&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;showChinese&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showChinese&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;你好&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backgound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showChinese&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;red&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我简单的画了一个数据和视图树的依赖关系图。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://oss-ata.alibaba.com/article/2024/02/e35c1d33-f7b5-4544-aa6a-4b75b5924bc2.png&quot; alt=&quot;Image Description&quot; width=&quot;600&quot; height=&quot;auto&quot; /&gt;&lt;/p&gt;

&lt;p&gt;从上面的代码可以看出，模型是通过 &lt;code class=&quot;highlighter-rouge&quot;&gt;@state&lt;/code&gt; 这种语法来映射到 SwiftUI 的视图树上的，那么 &lt;code class=&quot;highlighter-rouge&quot;&gt;@state&lt;/code&gt; 的作用我们下面来简单分析下。&lt;/p&gt;

&lt;h3 id=&quot;41-数据驱动的标记语法&quot;&gt;4.1 数据驱动的标记语法&lt;/h3&gt;

&lt;p&gt;SwiftUI 中&lt;code class=&quot;highlighter-rouge&quot;&gt;@state&lt;/code&gt; 这种语法，其实属于属性包装器 (Property Wrapper),也是 Swift 语言的一个语法特性。如果想要详细了解这个语法的话，可以参看 &lt;a href=&quot;[https://](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties/)&quot;&gt;官方文档&lt;/a&gt;。它的作用就是当标记的数据有变化时，通知视图树来刷新 UI。&lt;/p&gt;

&lt;p&gt;类似这种属性包装器的语法，SwiftUI 还有很多个，&lt;code class=&quot;highlighter-rouge&quot;&gt;@State @StateObject @Binding @ObservedObject&lt;/code&gt; 。记住这些语法都是用来做数据驱动 UI 使用的。至于这些属性包装器具体如何使用，下面列出来了一些规则可供参考。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;初始化属性
 如果属性无法在声明时进行初始化，而是需要接收父节点的数据，应该考虑使用 &lt;code class=&quot;highlighter-rouge&quot;&gt;@Binding&lt;/code&gt; 或 &lt;code class=&quot;highlighter-rouge&quot;&gt;@ObservedObject&lt;/code&gt;，而不是 &lt;code class=&quot;highlighter-rouge&quot;&gt;@State&lt;/code&gt; 或 &lt;code class=&quot;highlighter-rouge&quot;&gt;@StateObject&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;普通属性 vs. 属性包装器
 尽可能在视图中使用普通属性，当仅需将值传递到视图中时，不需要使用属性包装器。&lt;/li&gt;
  &lt;li&gt;@State
 当视图需要对一个值进行读写访问，并且该值作为本地的私有视图状态时，应该使用 &lt;code class=&quot;highlighter-rouge&quot;&gt;@State&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;@Binding
 当视图需要对一个值进行读写访问，但该值不属于视图本身，而是由外部传递进来时，应该使用 &lt;code class=&quot;highlighter-rouge&quot;&gt;@Binding&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;@StateObject 和 @ObservedObject
 当视图需要以对象的形式拥有状态，并且这个状态是本地私有的视图状态时，应该使用 &lt;code class=&quot;highlighter-rouge&quot;&gt;@StateObject&lt;/code&gt;。当需要从外部传入一个对象时，应该使用 &lt;code class=&quot;highlighter-rouge&quot;&gt;@ObservedObject&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;注意:上面这样原则其实是 iOS17 之前需要遵守的，之后语法有了相应的更新，使用起来会更加的简单。等下我们就会讲到 iOS17 带来的更新。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;在我们定义模型对象时，还有个重要的概念要讲 Identifiable Protocol （身份协议）。因为 SwiftUI 所有的视图树都是值类型，不同于 UIKit 每个 UIView 在内存中都有唯一的地址。而 SwiftUI 中的 View 是和映射的模型绑定到一起，框架是依靠模型的不同来确定是否为不同的视图，而如何确定模型的身份就是靠 Identifiable Protocol 这个协议，我们我就拿官网的示例代码展示下，Identifiable 作用。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Animal&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Pet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Identifiable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Animal&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UUID&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UUID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;dataBaseID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;FavoritePets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Pet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;List&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;ForEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;PetView&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面这个方式构建的 SwiftUI 的视图会出现一个问题，每次向 pets 中添加新的模型时，整个列表都会刷新，所有的视图重新创建了。再看下面的代码。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;FavoritePets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Pet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;List&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;ForEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dataBaseID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;PetView&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果在创建视图时，改成上面的实现方式，给一个确定的 id 例如 dataBaseID 是一个持久化到数据库中。就不会出现上述的问题，所以我们在实现模型的 Identifiable 协议时，一定要牢记他就是对应 SwiftUI 的视图。&lt;/p&gt;

&lt;p&gt;如果想要详细了解数据如何驱动视图树的原理，建议参看苹果 WWDC &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10022/&quot;&gt;揭开 SwiftUI 的神秘面纱&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;42-ios17-之后的变化&quot;&gt;4.2 iOS17 之后的变化&lt;/h3&gt;

&lt;p&gt;为什么要讲 iOS17 之后 SwiftUI 的变化呢？因为 visionOS 使用的 SwiftUI 就是 iOS17 之后的。我们来看下对比 iOS17 前后状态驱动语法的变化。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;iOS17 之前&lt;/li&gt;
&lt;/ol&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;类型&lt;/th&gt;
      &lt;th&gt;根状态&lt;/th&gt;
      &lt;th&gt;子状态&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;值类型&lt;/td&gt;
      &lt;td&gt;@State&lt;/td&gt;
      &lt;td&gt;@Binding&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;引用类型&lt;/td&gt;
      &lt;td&gt;@StateObject&lt;/td&gt;
      &lt;td&gt;@ObservedObject&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ol&gt;
  &lt;li&gt;iOS17 之后&lt;/li&gt;
&lt;/ol&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;类型&lt;/th&gt;
      &lt;th&gt;根状态&lt;/th&gt;
      &lt;th&gt;子状态&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;值类型&lt;/td&gt;
      &lt;td&gt;@State&lt;/td&gt;
      &lt;td&gt;@Binding&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;引用类型&lt;/td&gt;
      &lt;td&gt;@State&lt;/td&gt;
      &lt;td&gt;@Bindable&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;从上面可以看出来 iOS17 之后 SwiftUI 状态绑定的用法更简单了。iOS17 之前如果你想要一个全局的数据源，驱动 UI 变化需要写下面的代码？&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SwiftUI&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Combine&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 1. 定义全局数据源&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;GlobalData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ObservableObject&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@Published&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;counter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;shared&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;GlobalData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 单例对象&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ContentView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 2. 订阅数据变化&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@ObservedObject&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;globalData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;GlobalData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;VStack&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Counter: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;globalData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;counter&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Increment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;GlobalData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;counter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 修改全局数据源的值&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;iOS17 之后的 Swift 语法有个重大更新，就是支持了宏。然后在 SwiftUI 中， 新增了 @Observable 宏定义，开发者完全不用使用 Combine 框架的数据绑定能力了，只需要简单的写下面的代码：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;@Observable&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;GlobalData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;counter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;shared&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;GlobalData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 单例对象&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再使用 iOS17 之后 SwiftUI 为 View 新增的 &lt;code class=&quot;highlighter-rouge&quot;&gt;environment&lt;/code&gt; API，创建 View 的时候简单的调用  &lt;code class=&quot;highlighter-rouge&quot;&gt;ContentView.environment(self.GlobalData.shared)&lt;/code&gt;，然后视图就可以用下面的代码监听数据变化。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@available&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iOS&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;17.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;macOS&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;14.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tvOS&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;17.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;watchOS&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;10.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Observable&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ContentView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 订阅数据变化&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;GlobalData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;globalData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;GlobalData&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;VStack&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Counter: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;globalData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;counter&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Increment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;GlobalData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;counter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 修改全局数据源的值&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那如果说我们创建的 Model 某些属性不需要监听怎么办？比较好的方式定义为 let 常量，如果说需要变量存储中间状态可以使用 &lt;code class=&quot;highlighter-rouge&quot;&gt;@ObservationIgnored&lt;/code&gt; 这种标记语法。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;@Observable&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;GlobalData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;counter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@ObservationIgnored&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;tempValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;shared&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;GlobalData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 单例对象&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样当 tempValue 变化时就不会触发通知，对控制 UI 频繁刷新很有用途。&lt;/p&gt;

&lt;h2 id=&quot;5-总结&quot;&gt;5. 总结&lt;/h2&gt;

&lt;p&gt;如果上面说的这些能力都掌握的话，开发 SwiftUI 基本就没什么问题了。虽然说 SwiftUI 可以大大节省 UI 的开发量。但是开发过程中遇到的棘手问题就是调试。在 UIKit 所有的 UIView 都有内存地址，来确定某个视图什么时候创建、销毁、以及放置到那里。所以 UIKit 调试的时候，只要看下内存地址就很方便定位是那个视图，但是 SwiftUI 中的视图 View 你无法打印内存地址，就像上文说的你只能跟踪他的模型状态，但是在 Debug 过程中，往往视图的变化堆栈不会有模型变化的堆栈，在调试一些 UI 层级问题时就会遇到很多问题。&lt;/p&gt;</content><author><name>mengtnt</name></author><summary type="html">1. 背景 在我们开发 Vision Pro 开发之前，先大致了解下 SwiftUI 框架是非常有必要的，不然很多系统的新特性苹果往往只会提供 SwiftUI 的演示代码，甚至有些 API 只能 SwiftUI 才能使用。所以想要更好的适配 Vision Pro 最好的方式是用 SwiftUI 进行开发。</summary></entry><entry><title type="html">灵动岛按钮交互事件原理探究</title><link href="https://mengtnt.com/2024/01/01/widgets.html" rel="alternate" type="text/html" title="灵动岛按钮交互事件原理探究" /><published>2024-01-01T02:00:19+00:00</published><updated>2024-01-01T02:00:19+00:00</updated><id>https://mengtnt.com/2024/01/01/widgets</id><content type="html" xml:base="https://mengtnt.com/2024/01/01/widgets.html">&lt;h1 id=&quot;问题背景&quot;&gt;问题背景&lt;/h1&gt;

&lt;p&gt;苹果 WWDC2023 Widgets 增加了新的交互能力，iOS17 以前 Widgets 只能展示，无法响应 UI 上所有的按钮事件，只能跳转到主 App 中。iOS17 之后，通过这个新的交互能力，就可以实现 Widgets 上面的按钮点击事件。如果感兴趣可以看下苹果的&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10028/&quot;&gt;WWDC视频&lt;/a&gt;和&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/adding-interactivity-to-widgets-and-live-activities&quot;&gt;官方文档&lt;/a&gt;，下面会简单的介绍下苹果实现灵动岛 (LiveActivity Widget) 上按钮响应的方式和开发的过程中遇到的问题。&lt;/p&gt;

&lt;h2 id=&quot;1-灵动岛如何增加按钮响应事件&quot;&gt;1. 灵动岛如何增加按钮响应事件&lt;/h2&gt;

&lt;p&gt;根据苹果的 Widgets 的逻辑，也就是我们写的所有 SwiftUI 的代码会放在 Widgets Extension 的进程中，但是在 SwiftUI 代码中所有的异步事件都无法执行，例如按钮事件、下载事件等等。可以看下在 Widgets Extension 中编写下面的代码，&lt;code class=&quot;highlighter-rouge&quot;&gt;print(&quot;test&quot;)&lt;/code&gt; 这行代码是没法执行的。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;DynamicIslandExpandedRegion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Start&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;猜测苹果在 Extension 进程中，把所有异步事件注册的能力给拔除了。&lt;/p&gt;

&lt;p&gt;那 iOS17 之后灵动岛的 Widgets 又是怎么来响应异步事件呢？苹果是用 Intent 的方式来执行的，这样需要注册一个 Intent 类，然后来执行事件的代码。示例中按钮注册的代码如下：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;DynamicIslandExpandedRegion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;intent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ConfigurationAppIntent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;systemName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;plus.circle.fill&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在接收的模块需要实现一个 Intent 的类，如下：&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ConfigurationAppIntent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;WidgetConfigurationIntent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;LocalizedStringResource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Configuration&quot;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;IntentDescription&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;This is an example widget.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;perform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;IntentResult&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;ShareData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;updateButtonStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;按照上述的方式，按钮点击时，就可以执行 &lt;code class=&quot;highlighter-rouge&quot;&gt;updateButtonStatus&lt;/code&gt; 这个方法了，下面是 &lt;code class=&quot;highlighter-rouge&quot;&gt;updateButtonStatus&lt;/code&gt; 这个方法执行的逻辑，就是在共享的 group 中存储数据，然后主进程就可以读取数据的变化了。&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ShareData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;appGroup&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;com.mengtnt.myGroup&quot;&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;currentActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Activity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;DynamicIslandAttributes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UserDefaults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;suiteName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appGroup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)?&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;forKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as?&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;updateButtonStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;UserDefaults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;suiteName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appGroup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)?&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;forKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里展示下运行效果。
&lt;img src=&quot;https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/36818385-adb4-4aea-9839-5034a33edcd2.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;2-遇到事件无响应的问题&quot;&gt;2. 遇到事件无响应的问题&lt;/h2&gt;

&lt;p&gt;由于钉钉都是基于 Framework 开发的，并不是全源码编译。所以这里模拟了钉钉的方式，写了如下的工程。所有的 Intent 和 LiveActivity 的代码都放到了一个叫 Appintent 的 Framework 下。然后相应的类引用这个 Framework，这个工程中任何源码都没改动。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/a8d66e3a-d13a-49e7-9a91-240e07846ae5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后神奇的一幕就发生了，灵动岛上的按钮事件没有任何反应。并且相应的代码也不会调用。效果如下:
&lt;img src=&quot;https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/15b84045-781d-4763-97aa-697d238d1185.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后推测是不是用 Framework 的方式，苹果把 Intent 的代码调用给裁剪掉了，然后用 hopper 分别打开了两种方式下生成的最终产物，分别搜索按钮响应事件调用的 &lt;code class=&quot;highlighter-rouge&quot;&gt;updateButtonStatus&lt;/code&gt; 方法.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/b6f0b3b1-2922-4649-ae47-2f706a7ae3ad.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后通过 hopper 跳转到调用的函数地址，就会发现调用的函数都是 Intent 类的 perform 方法。所以 Intent 类的 perform 方法其实都已经 link 到了目标 Target 中，只是两种方式生成的符号地址不一样而已。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/cd9f1768-5fbf-4c79-988a-d67bd2ad3b09.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/2b51b1a6-71df-41ee-9b6e-9637351933be.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;所以应该不是先前的猜测，用 Framework 的方式执行的目标代码应该没有被裁剪。那么执行的代码都生成了，又会是什么原因造成按钮事件无响应呢？&lt;/p&gt;

&lt;h2 id=&quot;3-找出-intent-调用的真相&quot;&gt;3. 找出 Intent 调用的真相&lt;/h2&gt;

&lt;p&gt;然后就继续对比了下全源码编译的产物和 Framework 方式编译的最终产物。发现全源码编译在 Extension Target 下面多了这个文件夹 MetaData.appintents，但是 Framework 方式并没有。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/d6ed9ea9-2012-4ef8-9ef8-a459a0033e3d.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;打开这个文件，发现里面有一个 extract.actionsdata 的 json 文件，里面注册了一个 mangledTypeName 名字。&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;generator&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;xcode-tools&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;15.0&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;enums&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;queries&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;autoShortcuts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;entities&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;negativePhrases&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;actions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;ConfigurationAppIntent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;outputFlags&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;isDiscoverable&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;typeSpecificMetadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;requiredCapabilities&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;effectiveBundleIdentifiers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;systemProtocolMetadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;com.apple.link.systemProtocol.WidgetConfiguration&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;empty&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;descriptionMetadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;searchKeywords&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;descriptionText&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;This is an example widget.&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mangledTypeNameByBundleIdentifier&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Configuration&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;identifier&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ConfigurationAppIntent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;systemProtocols&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;com.apple.link.systemProtocol.WidgetConfiguration&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;authenticationPolicy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;presentationStyle&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mangledTypeName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;22DynamicIslandExtension22ConfigurationAppIntentV&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;availabilityAnnotations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;LNPlatformNameWildcard&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;introducedVersion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;openAppWhenRun&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;shortcutTileColor&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;mangled type name 又是什么，简单的讲就是编译器根据用户的代码生成的特殊符号，里面包含了很多信息例如类型、接收的参数，返回类型，模板的情况等等。如果感兴趣可以详细了解 Swift 语言关于 mangled type 的原理。说白了 mangledTypeName 就是在编译器 link 的时候，方便获取函数调用的真正地址。用 hopper 打开，搜索定义的符号名称 &lt;code class=&quot;highlighter-rouge&quot;&gt;22DynamicIslandExtension22ConfigurationAppIntentV&lt;/code&gt;。这个名字正是前面看到的 Intent 类的地址符号。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/5195cf4e-7a51-4832-a794-052753095045.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;通过此 mangledTypeName 就可以找到类相应的内存地址，从而找到执行的 perform 方法。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/6ba4e051-fa78-4ec7-bd3f-69cf655f5ade.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;那通过同样的方式，就寻找了下 Framework 方式下的 Extension 的产物，然后使用 hopper 打开，寻找相应的 Intent 类的符号，发现为 &lt;code class=&quot;highlighter-rouge&quot;&gt;9AppIntent013ConfigurationaB0V&lt;/code&gt;，如下图，这里可以看出来这个符号是带有 Framework 名称 AppIntent。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/8ead5539-eadf-4fa7-8986-9e8415730c8b.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后用类似的方式，在 Extension Target 下面创建了 metadata.appintents 文件夹，然后配置了如下数据：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;generator&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;xcode-tools&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;15.0&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;enums&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;queries&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;autoShortcuts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;entities&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;negativePhrases&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;actions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;ConfigurationAppIntent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;outputFlags&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;isDiscoverable&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;typeSpecificMetadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;requiredCapabilities&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;effectiveBundleIdentifiers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;systemProtocolMetadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;com.apple.link.systemProtocol.WidgetConfiguration&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;empty&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;descriptionMetadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;searchKeywords&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;descriptionText&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;This is an example widget.&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mangledTypeNameByBundleIdentifier&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Configuration&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;identifier&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ConfigurationAppIntent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;systemProtocols&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;com.apple.link.systemProtocol.WidgetConfiguration&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;authenticationPolicy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;presentationStyle&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mangledTypeName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;9AppIntent013ConfigurationaB0V&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;availabilityAnnotations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;LNPlatformNameWildcard&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;introducedVersion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;openAppWhenRun&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;shortcutTileColor&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;运行后，果然按钮事件可以响应了，和全源码编译的运行结果一模一样。&lt;/p&gt;

&lt;h2 id=&quot;4-总结&quot;&gt;4. 总结&lt;/h2&gt;

&lt;p&gt;通过上面的尝试，我们可以总结出来苹果 Intent 的运行原理，其实有点像 patch 原理，把异步事件响应的代码，用 metaData.appintents 中定义的符号地址替换，用户运行时就执行了 metaData.appintents 中定义的 action 方法。&lt;/p&gt;

&lt;p&gt;其实苹果在开发者文档的中描述过 Intent 的大致执行过程，但是具体的实现方式并没有描述。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/0393b314-2fcd-48b7-b5f7-eb0e0155a70e.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;通过进一步验证了，把 Intent 类放到了主 App 的 Target 中，编译后，在主进程的 Target 中同样会生成一个 metaData.appintents，然后运行后，事件响应的代码就在主进程的空间中执行了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/e2796830-7ab6-499a-b3b7-cc3796c4f256.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;所以总结下，Widgets 的整个生命周期的事件循环，本质上都是系统托管的。然后如何执行用户的异步事件呢？就像上面说的，在编译 Widgets 代码的时候，会根据注册的异步事件，然后生成 Intent 类的符号地址，然后会把此符号地址放到 metaData.appintents 文件夹下 extract.actionsdata 这个 json 文件中。当异步事件调用时，系统就会替换为 Intent 类注册的 perform 方法。不过发现 xcode 在编译 Widgets Extension 时，如果 Target 源码中没有 Intent 这个类就不生成调用地址的符号数据了，从而造成使用 Framework 这种方式，无法响应异步事件，不知道是苹果特意这样设计的，还是给我们开发者埋的小惊喜😂。&lt;/p&gt;</content><author><name>mengtnt</name></author><summary type="html">问题背景</summary></entry><entry><title type="html">视频帧处理</title><link href="https://mengtnt.com/2023/06/17/video-process.html" rel="alternate" type="text/html" title="视频帧处理" /><published>2023-06-17T03:01:06+00:00</published><updated>2023-06-17T03:01:06+00:00</updated><id>https://mengtnt.com/2023/06/17/video-process</id><content type="html" xml:base="https://mengtnt.com/2023/06/17/video-process.html">&lt;p&gt;当我们做美颜、虚拟背景、虚拟人偶等功能时，一般都是需要对 iOS 相机帧进行前置处理。如果做过 iOS 开发的话，很快可以写出来下面的处理过程代码。&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;captureOutput&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AVCaptureOutput&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;captureOutput&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;didOutputSampleBuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CMSampleBufferRef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sampleBuffer&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;fromConnection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AVCaptureConnection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CVPixelBufferRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pixelBuffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CMSampleBufferGetImageBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sampleBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CVPixelBufferRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resultPixelBuffe&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;renderPixbuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resultPixelBuffe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;上面的代码看起来没啥问题，不出意外应该也可以顺利的运行起来。下面我们就逐步来看会遇到什么问题。&lt;/p&gt;

&lt;h2 id=&quot;视频卡顿问题&quot;&gt;视频卡顿问题&lt;/h2&gt;

&lt;p&gt;当你写的代码运行的时间比较久时，手机发烫性能下降时会发现延时感非常强烈，看到的自己的画面很可能是5秒之前的画面。主要的原因就是性能下降时，手机的硬件处理速度下降，相机的视频帧的采集速度和处理速度不能匹配，造成了视频帧堆积引起的问题。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/6e785b9273e874349d5d06bb8b9a66a4cb50ca10e3feebe3f25a0d27c676b718.png&quot; alt=&quot;图 1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图很形象的展示了这个过程，系统相机采集帧的线程队列优先级往往比较高，当遇到我们的帧处理线程时。相当于高速公路上的汽车突然来到了省道上，如果系统性能比较好时，高速公路不繁忙那么自然不会拥堵，当系统性能下降时很容易遇到上图示例展示的拥堵，这时候用户看到的视频帧自然就会延时的很厉害。&lt;/p&gt;

&lt;h2 id=&quot;线程的优化&quot;&gt;线程的优化&lt;/h2&gt;

&lt;p&gt;既然遇到了拥堵问题，那我们怎么优化呢？第一个想到的是，不要卡主系统相机采集线程的回调，通过设置另一个线程队列来处理我们的视频帧，自然会写出下面的代码。&lt;/p&gt;
&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;_frameQueue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatch_queue_create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;org.dingtalk.cameravideocapturer.video&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DISPATCH_QUEUE_SERIAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 提升视频帧处理线程队列的优先级，&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dispatch_set_target_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_frameQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatch_get_global_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DISPATCH_QUEUE_PRIORITY_HIGH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;captureOutput&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AVCaptureOutput&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;captureOutput&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;didOutputSampleBuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CMSampleBufferRef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sampleBuffer&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;fromConnection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AVCaptureConnection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CVPixelBufferRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pixelBuffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CMSampleBufferGetImageBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sampleBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CVBufferRetain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;dispatch_async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frameQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;CVPixelBufferRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resultPixelBuffe&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;renderPixbuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resultPixelBuffe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;CVBufferRelease&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这种想法是没有问题的，但是现实很残酷，虽然你切换了处理线程，并且也提升了线程的优先级，相当于吞吐量增加了。但是遇到性能下降时，尤其像美颜、虚拟背景处理视频帧花费的时间会比较长。会造成的问题是，有大量的视频帧囤积到内存中，然后等待你的 &lt;code class=&quot;highlighter-rouge&quot;&gt;frameQueue&lt;/code&gt; 队列去处理。如果观察内存的变化情况就如下图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/aec899a03a3f37fb837decdca95cfb498e79daf1114cd7e2c1a4909216f6447b.png&quot; alt=&quot;图 2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;并且在手机发烫，CPU 性能下降时，每帧视频处理时长会越来越长，导致内存不断增加形成恶性循环，最终的结果就是 OOM 程序崩溃。
为何内存会囤积到内存中，等待 &lt;code class=&quot;highlighter-rouge&quot;&gt;frameQueue&lt;/code&gt; 线程队列执行呢？这就涉及到我们使用的一个操作 &lt;code class=&quot;highlighter-rouge&quot;&gt;dispatch_async&lt;/code&gt;。下面展示下 &lt;code class=&quot;highlighter-rouge&quot;&gt;dispatch_async&lt;/code&gt; 的源码实现。&lt;/p&gt;
&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; 
&lt;span class=&quot;nf&quot;&gt;dispatch_async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dispatch_queue_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatch_block_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatch_continuation_s&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;contin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dispatch_continuation_init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;contin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_dispatch_async_f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;contin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DISPATCH_INVOKE_ASYNC_BIT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;DISPATCH_NOINLINE&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;_dispatch_async_f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dispatch_queue_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatch_continuation_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctxt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatch_function_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dc_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;kt&quot;&gt;uint64_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dc_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint64_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dc_flags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dq_items_tail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;do_next&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dq_items_tail&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slowpath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dq_width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 如果是串行队列,直接执行任务或唤醒runloop&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_dispatch_queue_push_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 如果是并发队列,直接将任务添加到队列尾部&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;我们总结下，&lt;code class=&quot;highlighter-rouge&quot;&gt;dispatch_async&lt;/code&gt; 主要做了下面两个事情。&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;如果 dq 是串行队列,它会直接执行 dc 中的任务或唤醒 runloop 来执行任务。&lt;/li&gt;
  &lt;li&gt;如果 dq 是并发队列,它只会简单地将 dc 添加到队尾,等待后续被线程查找并执行。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;可以看出 &lt;code class=&quot;highlighter-rouge&quot;&gt;dispatch_async&lt;/code&gt; 的主要工作是将任务加入队列,并根据队列类型来决定是否直接执行任务。所以当我们定义一个串行队列时。本质上就是不停的往队列中放置数据，如果放置的队列中有大数据，而我们又没做相应的丢弃操作，就很容易引起内存堆积问题。&lt;/p&gt;

&lt;h2 id=&quot;丢帧优化&quot;&gt;丢帧优化&lt;/h2&gt;

&lt;p&gt;为了防止上述的 OOM 的情况，最容易想到的就是对堆积的队列做丢帧的处理。可以通过设置丢帧的间隔，比如设置0.1秒间隔，如果处理不完就丢弃掉后面来的视频帧，可以写如下的代码。&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;_processSemaphore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatch_semaphore_create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;captureOutput&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AVCaptureOutput&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;captureOutput&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;didOutputSampleBuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CMSampleBufferRef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sampleBuffer&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;fromConnection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AVCaptureConnection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CVPixelBufferRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pixelBuffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CMSampleBufferGetImageBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sampleBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CVBufferRetain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;patch_async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frameQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dispatch_semaphore_wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processSemaphore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;dispatch_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DISPATCH_TIME_NOW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int64_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSEC_PER_SEC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;CVBufferRelease&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;CVPixelBufferRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resultPixelBuffe&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;renderPixbuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resultPixelBuffe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;CVBufferRelease&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dispatch_semaphore_signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processSemaphore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;上述的代码，可以用下图形象的展示:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/91f6edf8fe09d458fc666da1f47f648a0b62037ea043071c865ed4fbb901e5e2.png&quot; alt=&quot;图 9&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里相当于给高速公路设置了一个分流站，不合格的车辆直接当场扔掉(这样有点残暴，当然这里只是假设)。似乎用这种方式可以解决内存堆积问题。我们的程序用这种方式继续运行，会带来另一个问题。虽然内存不会持续增加了，但是内存会出现过山车的情况忽上忽下。如果用 instument 观察就如下面的现象。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/7faaf3cc155c130962baf6fabf4dddaff71f92fcfb49012967fa4db6af1cb4ff.png&quot; alt=&quot;图 10&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这种情况虽然不至于让程序很快崩溃，但是也是在危险的边缘不停的试探，一旦一次触发到底线还是会崩溃的。那我们如何解决呢。&lt;/p&gt;

&lt;h2 id=&quot;缓存队列&quot;&gt;缓存队列&lt;/h2&gt;

&lt;p&gt;从上面的代码可以看到，之所以形成了过山车内存的问题，并不是采集问题引起的。因为采集线程已经做了丢帧的操作。我们把问题用下图描述。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/3e4d1941c22bc72457c2f5ba4dc26d22b841ea55201020744a272a68e5b6ecb4.png&quot; alt=&quot;图 4&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以看出主要原因是处理线程完成后，在渲染时由于采集和渲染在同一个 &lt;code class=&quot;highlighter-rouge&quot;&gt;framequeue&lt;/code&gt; 线程中，会造成我们最开始描述的视频帧拥堵问题。那我们如何解决这个问题，和上面描述的优化逻辑一样，首先要把采集的线程和渲染线程分离开，然后再做丢帧的操作。这里我们可以通过增加一个缓存队列来做，实现代码如下：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;CVBufferRetain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CVPixelBufferRef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;willDropPixelBuffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffersLock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kRTCMaxDropPixBufferFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;willDropPixelBuffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__bridge&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CVPixelBufferRef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffers&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;objectAtIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffers&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;removeObjectAtIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffers&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;addObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__bridge&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBuffersLock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;willDropPixelBuffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CVBufferRelease&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;willDropPixelBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;那为何采集的时候通过设置 0.1 秒的时间间隔来丢帧，而渲染要通过缓存队列来丢帧呢？其实本质上一样的，只是丢弃帧的逻辑不太一样而已，因为相机采集的帧回调的数据比较多，通过时间间隔丢帧可以更好的控制帧率，防止画面抖动太厉害。而渲染时就没必要这么精准的控制，通过丢弃过老的帧来防止内存抖动问题就可以了。然后我们优化后视频帧的整个处理过程就如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/44a65e2f89e402ecce91a8cd8cd2c96063ee8500c963913751863b813e08e7a8.png&quot; alt=&quot;图 5&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我们总结下，解决视频帧处理遇到的问题，主要通过下面两个手段来防止：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;分离相机帧采集的线程队列和渲染的队列 (frameQueue、renderQueue)，防止采集线程处理慢时造成渲染线程被卡住。&lt;/li&gt;
  &lt;li&gt;在两个线程队列切换时，增加数据丢帧逻辑防止内存 OOM。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最终改造后的流程图如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/eae0c47b0d89e435451f0c552d09e58af5c4d60ef00aa1dd8d43f45067ed6ad6.png&quot; alt=&quot;图 6&quot; /&gt;&lt;/p&gt;

&lt;p&gt;改造之前和改造之后，用 Instument 观察内存的抖动情况，可以明显的看到区别。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/76808d7ec2dff7a1bddc268542f2511c8406b7dae9e02766c0591553bf3ed3d2.png&quot; alt=&quot;图 7&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;

&lt;p&gt;上述虽然描述的是视频帧的处理优化过程。其实所有大的内存数据管道化处理时，都应该遵循下面的基本原则：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;各个功能模块分别用不同的线程来处理，这样彼此互相独立不会相互影响数据的处理过程，避免拥塞卡顿问题。&lt;/li&gt;
  &lt;li&gt;在功能线程切换时传递的内存大数据，通过设置缓存 Buffer 避免引起内存问题，防止内存过多造成 OOM。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;下面我们看下业界比较优秀的 WebRTC 音视频数据的处理过程，如下图所示:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/3ef26960fa812d2a4444c1c773e4b65a31cffaefd84bbed0ff2f39ba830b566e.png&quot; alt=&quot;图 8&quot; /&gt;&lt;/p&gt;

&lt;p&gt;WebRTC 的视频采集、混合、编码、发送的过程，都是分别使用不同线程，并且都有相应的 Buffer 做缓存操作，非常符合上面我们视频帧优化的整个过程。所以我们在做这种大内存数据处理时尤其要铭记上面提到的两个原则。&lt;/p&gt;</content><author><name>mengtnt</name></author><summary type="html">当我们做美颜、虚拟背景、虚拟人偶等功能时，一般都是需要对 iOS 相机帧进行前置处理。如果做过 iOS 开发的话，很快可以写出来下面的处理过程代码。</summary></entry><entry><title type="html">重构</title><link href="https://mengtnt.com/2023/03/20/refactor.html" rel="alternate" type="text/html" title="重构" /><published>2023-03-20T06:18:02+00:00</published><updated>2023-03-20T06:18:02+00:00</updated><id>https://mengtnt.com/2023/03/20/refactor</id><content type="html" xml:base="https://mengtnt.com/2023/03/20/refactor.html">&lt;p&gt;重构是一个老生常谈的问题，并且也有大量的书籍介绍重构的方法。那如何重构代码？如何保证重构的代码不出问题？重构后的代码如何防止腐化？我先贴出来 ChatGPT 如何回答的，来感受下她的强大。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;如何重构代码&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;提取方法：如果你有一段过长或过于复杂的代码，可以将其提取为独立的方法，使其更易读且可重复使用。&lt;/li&gt;
    &lt;li&gt;重命名变量和方法：使用描述性的名称命名变量和方法，使代码更容易理解。&lt;/li&gt;
    &lt;li&gt;移除重复代码：查找重复的代码块，并将其提取为可重复使用的方法。&lt;/li&gt;
    &lt;li&gt;简化条件语句：如果条件语句过于复杂，将其分解为更小、更易处理的部分。&lt;/li&gt;
    &lt;li&gt;使用更好的数据结构：如果使用的数据结构效率低下或不能满足需求，考虑使用更好的数据结构。&lt;/li&gt;
    &lt;li&gt;将代码分解成模块：将代码分解成具有特定职责的较小模块，每个模块都有自己的一组测试。&lt;/li&gt;
    &lt;li&gt;移除无用代码：查找不再使用的代码，并将其删除以简化代码库并使其更易维护。
这些只是重构代码的几个示例。关键在于不断寻找改进代码的方法，使其随着时间推移更易维护。&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;ChatGPT 上的回答和很多书籍和教程中讲的大同小异，其实有时候描述文字会很抽象。这里我拿自己经历的一个例子说明。当然不是 ChatGPT 给的例子😂。&lt;/p&gt;

&lt;h2 id=&quot;简单功能的开发&quot;&gt;简单功能的开发&lt;/h2&gt;

&lt;p&gt;这是很常见的成员列表页面展示功能。我们先看第一个版本的写法。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;
classDiagram
class MemberListViewController {
  -MemberListView view
  -loadView()
}

class MemberListView{ 
  -renderView(List&amp;lt;MemberData&amp;gt; data)
}

class MemberData {
  -String name
  -Int status
}

MemberListViewController *-- MemberListView: 1..1

MemberListView ..&amp;gt; MemberData: 1..*

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下面用伪代码的方式展示下，调用的流程。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;MemberListViewController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loadView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;RequestData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MemberData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MemberData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dataList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;MemberListViewController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MemberListViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;vc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loadView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其实这种 UI 开发的逻辑再简单不过了，获取数据然后把数据渲染到相应的 UI 组件上。我们下面来看下功能的发展过程。&lt;/p&gt;

&lt;h2 id=&quot;臃肿类的形成&quot;&gt;臃肿类的形成&lt;/h2&gt;

&lt;p&gt;随着此功能设计了不同的应用场景，例如从场景A进去应该获取服务A的数据然后展现，从场景B进去获取服务B的数据…
先来看如果按照原来的逻辑，需要写类似下面的大量代码。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;RequestAData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MemberData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MemberData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dataList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;RequestBData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MemberData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MemberData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dataList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这时候你就会发现 &lt;code class=&quot;highlighter-rouge&quot;&gt;Switch Case&lt;/code&gt; 中越来越多的数据请求和渲染代码。随着应用的场景越来越多，会发现 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListViewController&lt;/code&gt; 类变的越来越大。这时候如何重构?&lt;/p&gt;

&lt;p&gt;这里就要用到重构的一个重要原则：职责单一。优化代码就是把 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListViewController&lt;/code&gt; 大的类拆分，此类负责渲染视图，不要再负责数据请求了，网络请求相似的功能内聚到另外一个类中&lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListDataInteractor&lt;/code&gt; ，专门处理数据获取，这样可以减少单个类的大小，方便阅读。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;classDiagram

class MemberData {
  -String name
  -Int status
}

class MemberListDataInteractor { 
  - List&amp;lt;MemberData&amp;gt; dataList 
  - requestData(callback(List&amp;lt;MemberData&amp;gt; list))
}

class MemberListViewController {
  -MemberListView View
  -MemberListDataInteractor interactor
  -loadView()
}

class MemberListView{ 
  -View listView 
  -renderView(List&amp;lt;MemberData&amp;gt; data)
}

MemberListViewController *-- MemberListView: 1..1

MemberListViewController *-- MemberListDataInteractor: 1..1

MemberListDataInteractor *-- MemberData: 1..*

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个时候的调用过程可能是这样的。如下的伪代码：&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;MemberListViewController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loadView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;interactor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;requestData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MemberData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;MemberListView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;renderView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;MemberListViewController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loadView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;interactor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;requestData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MemberData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;MemberListView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;renderView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;MemberListViewController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MemberListViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;vc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loadView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个其实就是典型的 MVC 的结构，视图渲染、数据模型、模型组装分开，核心就是内聚不同的功能。有时候想一想阅读代码就跟我们看文章一样，如果不分段落，直接一个1000字的段落，相信很多人都不愿意看。职责单一原则的目标就是让人更愿意阅读你的代码，第一眼不至于被吓到。&lt;/p&gt;

&lt;h2 id=&quot;重复代码的优化&quot;&gt;重复代码的优化&lt;/h2&gt;

&lt;p&gt;随着功能的演化，用户的界面越来越复杂，需要大量的数据频繁的渲染到视图上。就会发现有大量视图渲染组装的代码。这类代码的特点是相似度很高，只是渲染到不同的视图上而已。这时候重构的另一个重要原则DRY，不要写重复的代码，就发挥作用了，我们只需要把重复的代码再抽象出一层，就可以减少大量相似的代码。
这个类可以叫做 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListDataPresentor&lt;/code&gt;,专门用来组装视图。只要简单的增加 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberViewModel&lt;/code&gt; 这种数据结构映射到 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListView&lt;/code&gt; 这种视图上，然后 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListDataPresentor&lt;/code&gt; 就负责数据组装。我们来看下这种结构。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;classDiagram

class MemberData {
  -String name
  -Int status
}

class MemberViewModel {
  -List&amp;lt;MemberData&amp;gt; dataList
}

class MemberListDataPresentor { 
  -MemberListDataInteractor interactor 
  - bind(MemberViewModel model,MemberListView view)
}

class MemberListViewController {
  -MemberListView View
  -MemberListDataPresentor presentor
  - loadView()
}

class MemberListView{ 
  -View listView 
  -renderView(MemberViewModel data)
}

MemberListDataPresentor *-- MemberViewModel : 1..1

MemberViewModel *-- MemberData: 1..*

MemberListView *-- MemberViewModel: 1..1

MemberListViewController *-- MemberListDataPresentor: 1..1


&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样使用的时候只要 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListDataPresentor&lt;/code&gt; 组装好数据，就不必再调用渲染了，可以直接通过 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberViewModel&lt;/code&gt; 映射到 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListView&lt;/code&gt; 上了。可以看到下面伪代码的调用过程。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;interactor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loadAllUserData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MemberData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;presentor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bindData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;listView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这其实就是 MVVM 的架构进化的过程。DRY 原则就是不要写重复的代码，就像写文章一样，千篇一律的文字没人愿意看一个道理。我们继续来看这个功能发展的情况。&lt;/p&gt;

&lt;h2 id=&quot;大量的耦合&quot;&gt;大量的耦合&lt;/h2&gt;

&lt;p&gt;随着功能越来越复杂，会发现 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListDataPresentor&lt;/code&gt; 这个类调用的接口会越来越多，既需要调用 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListDataInteractor&lt;/code&gt; 大量的请求接口来获取数据，同时也需要组装各种 Model 的数据，势必会造成大量接口暴露。这种各种复杂关系的调用，使阅读起越来越难。这时候软件工程的一个最好有的原则：任何工程问题，都可以通过增加一个中间层来解决。我们这里就需要增加工具类解耦，解耦的本质通过工具类拆分。使得依赖关系变为如下结构。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;classDiagram

MemberListDataPresentor ..&amp;gt; ColdObserval

MemberListDataInteractor ..&amp;gt; ColdObserval

MemberListViewController ..&amp;gt; ColdObserval

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有大量接口依赖的类，互相直接调用就可以用类似的方式。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;presentor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;addObserval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// bind data&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;interactor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其实这就是使用观察者模式来拆分。观察者模式的好处就是解耦，减少接口依赖，这样我们想要定义不同的 presentor 类时，也不必依赖各种具体的 interactor，只需要监听消息即可。例如 WebRTC 中重要的线程工具类 TaskQueue ，就是一个很好的解耦的拆分的工具。把编解码，采集，传输很好的解耦分离开。有了这个铺垫，我们最后来看下如何扩展功能。&lt;/p&gt;

&lt;h2 id=&quot;扩展新功能&quot;&gt;扩展新功能&lt;/h2&gt;

&lt;p&gt;试想下我们需要在 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListViewController&lt;/code&gt; 视图上增加新功能，不仅仅是显示 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListView&lt;/code&gt; 这一种视图，还能插入各种其他业务视图。&lt;/p&gt;

&lt;p&gt;正是因为有了工具类的拆分，这样所有的类都没有任何的依赖，不用暴露新接口，扩展就很容易了。具体可以通过代理模式来横向拆分。定义要扩展的代理类 Plugin，我们新功能只要实现 Plugin 定义的接口函数，就可以横向扩展所有的功能。我们看下类结构。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;classDiagram

class MemberListViewPlugin { 
  -View subView 
  -loadView()
}

class MemberListDataInteractorPlugin { 
  -List&amp;lt;MemberData&amp;gt; dataList 
  -requestData()
}

class MemberListDataPresentorPlugin { 
  -List&amp;lt;MemberData&amp;gt; dataList 
  -bindData()
}

MemberListDataPresentor *-- MemberListDataPresentorPlugin : 1..*

MemberListDataInteractor *-- MemberListDataInteractorPlugin : 1..*

MemberListView *-- MemberListViewPlugin : 1..*

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后我们的插件调用过程就如下：&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;MemberListDataPresentorPlugin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;presentorPlugin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MemberListDataPresentorPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;presentor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;registPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;presentorPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;MemberListDataInteractorPlugin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interactorPlugin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MemberListDataInteractorPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;interactor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;registPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interactorPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样每次增加新功能时，不需要更改原来的 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListDataPresentor&lt;/code&gt;、 &lt;code class=&quot;highlighter-rouge&quot;&gt;MemberListDataInteractor&lt;/code&gt; 任何代码，只需要添加插件的实现就可以了。这其实就是很多软件插件的架构模式。&lt;/p&gt;

&lt;p&gt;我们从上面这个例子里可以看到代码的演变，如何从 MVC 到 MVVM 再到最后插件化，这些过程让代码结构更加清晰容易阅读，防止代码腐化。&lt;/p&gt;

&lt;h2 id=&quot;重构的回顾&quot;&gt;重构的回顾&lt;/h2&gt;

&lt;p&gt;我们总结下上述的重构过程中几个关键的节点。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;当你发现一个类越来越大。
    &lt;blockquote&gt;
      &lt;p&gt;超过了1000行代码了，一定是需要拆分功能了。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;当你开发功能时，发现需要原来的类大量更改接口才能实现，我们就需要用工具类来解耦。&lt;/li&gt;
  &lt;li&gt;当添加新功能时，需要频繁更改一个类时。
    &lt;blockquote&gt;
      &lt;p&gt;这时候就需要用代理模式的插件来扩展你的类，这样就可以避免大量的修改逻辑，保证代码稳定性。例如我们经常看到的一些可插拔的插件系统，都是通过这种方式实现的。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;性能优化的代码太多时尤其注意，尽量不要暴露出来，因为性能优化的代码往往可读性比较差。
    &lt;blockquote&gt;
      &lt;p&gt;对于优化性能的代码，重构的时候我这里，尽量封装成内部的函数，而不要暴露给外部使用。比如定义了一个 Cache 资源的类，为了优化内存，这种最好不要把API暴露在外面，在内部消化最好。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;发现无用的功能代码及时的删除，防止进一步防止腐化
    &lt;blockquote&gt;
      &lt;p&gt;不及时删除的后果，会发现新功能调用以前移除的功能类的方法，这时候你想删除老功能代码时，你会发现欲哭无泪。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;对比 ChatGPT 重构的总结，总体原则一样，但是会更加具体一点。最后我想讲下关于重构代码时，如何保证稳定性的一些原则。我总结起来就是小步快走，保证稳定。在重构的过程中允许一定的冗余代码，增加灰度能力，当发现问题时可以及时回滚，等待重构的代码测试没问题了再删除。&lt;/p&gt;

&lt;p&gt;很多优秀的开源项目的代码不仅对代码的性能，也对代码的质量和可维护性要求很高，阅读起来就像欣赏优美的诗篇。屎山一样的代码从来不会有伟大的作品。我相信每个优秀的程序员，都不愿意把自己的代码变成屎山，但是罗马也不是一天能建成的，学会重构是必备的技能。&lt;/p&gt;</content><author><name>mengtnt</name></author><summary type="html">重构是一个老生常谈的问题，并且也有大量的书籍介绍重构的方法。那如何重构代码？如何保证重构的代码不出问题？重构后的代码如何防止腐化？我先贴出来 ChatGPT 如何回答的，来感受下她的强大。</summary></entry></feed>