<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>其它 on 土土哥的技术 Blog</title><link>https://tutuge.me/categories/%E5%85%B6%E5%AE%83/</link><description>Recent content in 其它 on 土土哥的技术 Blog</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>tutuge</copyright><lastBuildDate>Mon, 13 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tutuge.me/categories/%E5%85%B6%E5%AE%83/index.xml" rel="self" type="application/rss+xml"/><item><title>开源项目 Claude Code Notifier Plus：让 Claude Code 真正需要你时再提醒你</title><link>https://tutuge.me/2026/07/13/claude-code-notifier-plus/</link><pubDate>Sun, 12 Jul 2026 16:30:00 +0000</pubDate><guid>https://tutuge.me/2026/07/13/claude-code-notifier-plus/</guid><description>&lt;img src="https://tutuge.me/assets/2026/07/13/claude-code-notifier-plus/promo-banner.jpg" alt="Featured image of post 开源项目 Claude Code Notifier Plus：让 Claude Code 真正需要你时再提醒你" /&gt;&lt;h2 id="前言"&gt;&lt;a href="#%e5%89%8d%e8%a8%80" class="header-anchor"&gt;&lt;/a&gt;前言
&lt;/h2&gt;&lt;p&gt;最近这段时间用 Claude Code 比较多，慢慢发现一个很实际的问题：AI 写代码的时候人不应该一直盯着它。&lt;/p&gt;
&lt;p&gt;比如我让它跑一个重构任务，然后切到浏览器查资料、看文档，或者去另一个项目里继续处理事情。几分钟后切回来，发现它早就停在权限确认那里了；再或者任务已经结束，但我完全不知道。项目窗口一多，这种反复切换就更明显了。&lt;/p&gt;
&lt;p&gt;所以我写了一个小工具：&lt;code&gt;Claude Code Notifier Plus&lt;/code&gt;。它的目标很简单：&lt;strong&gt;当 Claude Code 需要我注意时，用系统通知提醒我，并且点击通知能回到对应的 VS Code 窗口或终端窗口&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Claude Code Notifier Plus" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://tutuge.me/assets/2026/07/13/claude-code-notifier-plus/promo-banner.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Github: &lt;a class="link" href="https://github.com/zekunyan/claude-code-notifier-plus" target="_blank" rel="noopener"
 &gt;https://github.com/zekunyan/claude-code-notifier-plus&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="为什么还要再做一个通知插件"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e8%bf%98%e8%a6%81%e5%86%8d%e5%81%9a%e4%b8%80%e4%b8%aa%e9%80%9a%e7%9f%a5%e6%8f%92%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;为什么还要再做一个通知插件
&lt;/h2&gt;&lt;p&gt;Claude Code 本身有 Hook 机制，理论上我们可以监听通知事件，然后调用系统通知工具即可。最开始我也是这么想的。&lt;/p&gt;
&lt;p&gt;但实际用下来有个坑：&lt;strong&gt;在 VS Code 扩展模式下，依赖 &lt;code&gt;Notification&lt;/code&gt; hook 的方式并不可靠&lt;/strong&gt;。Claude Code 的 VS Code 扩展会在自己的 webview 内部处理一部分交互，很多 CLI 模式下可用的通知逻辑，到了 VS Code 里就会悄悄失效。&lt;/p&gt;
&lt;p&gt;所以这个插件没有继续依赖单一的 &lt;code&gt;Notification&lt;/code&gt; hook，而是直接注册更具体的事件：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;场景&lt;/th&gt;
					&lt;th&gt;Claude Code Hook&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;code&gt;PermissionRequest&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;需要用户确认/回答&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Elicitation&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;主任务结束&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Stop&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Subagent 结束&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;SubagentStop&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这样 CLI 和 VS Code 扩展模式都能覆盖到。通知的触发条件也更清楚：不是“任何通知都弹一下”，而是围绕我真正需要回去处理的几个节点。&lt;/p&gt;
&lt;h2 id="功能点"&gt;&lt;a href="#%e5%8a%9f%e8%83%bd%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;功能点
&lt;/h2&gt;&lt;p&gt;当前版本是 &lt;code&gt;1.5.0&lt;/code&gt;，主要能力如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;双模式支持&lt;/strong&gt;：终端里的 Claude Code、VS Code 扩展里的 Claude Code 都能触发。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;纯 CLI fallback&lt;/strong&gt;：即使 VS Code 没有启动，也可以直接发系统通知。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;点击跳转&lt;/strong&gt;：macOS 上配合 &lt;code&gt;terminal-notifier&lt;/code&gt;，点击通知可以跳回对应 VS Code 项目窗口，或者 Terminal / iTerm2 / Warp 等终端窗口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务上下文&lt;/strong&gt;：通知里会带项目名和任务摘要，不用靠猜是哪一个窗口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多实例安全&lt;/strong&gt;：多个 VS Code 窗口同时运行时，通过 marker、lock、去重逻辑避免重复弹。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;焦点感知和延迟通知&lt;/strong&gt;：VS Code 已经在前台时可以不弹，也可以设置延迟，让 VS Code 内部 popup 有机会先被手动取消。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选中代码发送到 Claude Code&lt;/strong&gt;：选中代码后可以通过右键、快捷键、编辑器按钮，把 &lt;code&gt;@file#line&lt;/code&gt; 引用插入 Claude Code 输入框。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="快速安装"&gt;&lt;a href="#%e5%bf%ab%e9%80%9f%e5%ae%89%e8%a3%85" class="header-anchor"&gt;&lt;/a&gt;快速安装
&lt;/h2&gt;&lt;p&gt;VS Code 扩展商店搜索 &lt;code&gt;Claude Code Notifier Plus&lt;/code&gt;，或者命令行安装：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;code --install-extension tutuge.claude-code-notifier-plus
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果在 macOS 上希望支持点击通知后跳转到准确窗口，推荐安装：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install terminal-notifier
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;还需要给 &lt;code&gt;terminal-notifier&lt;/code&gt; 一次辅助功能权限：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;系统设置 -&amp;gt; 隐私与安全性 -&amp;gt; 辅助功能 -&amp;gt; 开启 terminal-notifier
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个权限的作用不是读取内容，而是允许它把对应窗口 raise 到前台。没有这个权限时，点击通知通常只能激活 App，不能精确切到某个项目窗口。&lt;/p&gt;
&lt;h2 id="整体架构"&gt;&lt;a href="#%e6%95%b4%e4%bd%93%e6%9e%b6%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;整体架构
&lt;/h2&gt;&lt;p&gt;先看整体流程：&lt;/p&gt;
&lt;p&gt;&lt;img alt="How It Works" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://tutuge.me/assets/2026/07/13/claude-code-notifier-plus/how-it-works.jpg"&gt;&lt;/p&gt;
&lt;p&gt;实现上拆成几块：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;extension.js&lt;/code&gt;：VS Code 扩展入口，负责安装 hook、监听 pipe 文件、读取配置、发通知。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hooks/notify.js&lt;/code&gt;：真正被 Claude Code hook 调用的脚本，负责把不同事件整理成统一 payload。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/hook-installer.js&lt;/code&gt;：自动修改 &lt;code&gt;~/.claude/settings.json&lt;/code&gt;，注册事件 hook。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/system-notification.js&lt;/code&gt;：封装 macOS / Windows / Linux 系统通知，以及 macOS 上的点击跳转。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/payload.js&lt;/code&gt;：解析 payload、过滤事件类型。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;简单来说，Claude Code 事件先进入 &lt;code&gt;notify.js&lt;/code&gt;，再写入一个固定文件；VS Code 扩展监听这个文件，最后根据配置决定要不要发系统通知。&lt;/p&gt;
&lt;p&gt;这个设计看起来多绕了一层，但它解决了一个实际问题：&lt;strong&gt;hook 脚本和 VS Code 扩展不在同一个进程里&lt;/strong&gt;。hook 是 Claude Code 启动的 Node 进程，扩展运行在 VS Code extension host 里，两边需要一个足够简单、跨环境稳定的通信方式。&lt;/p&gt;
&lt;h2 id="自动注册-hook"&gt;&lt;a href="#%e8%87%aa%e5%8a%a8%e6%b3%a8%e5%86%8c-hook" class="header-anchor"&gt;&lt;/a&gt;自动注册 Hook
&lt;/h2&gt;&lt;p&gt;插件启动时会把 hook 写到 &lt;code&gt;~/.claude/settings.json&lt;/code&gt;。核心逻辑大概是这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;VSCODE_HOOK_EVENTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;PermissionRequest&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Elicitation&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Stop&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;SubagentStop&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;buildHookEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notifyScriptPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;command&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`node &amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;notifyScriptPath&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;&amp;#34; # claude-code-notifier-plus`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里有两个细节：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;hook entry 里带了一个 sentinel：&lt;code&gt;# claude-code-notifier-plus&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;每次安装前会先清理旧的 managed hook，再写入新的 hook。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为什么要这么做？因为用户自己的 &lt;code&gt;settings.json&lt;/code&gt; 里可能已经有别的 hook，插件不能粗暴覆盖整个 hooks 配置。sentinel 的作用就是只识别自己管理的那一段：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;isManaged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hookEntry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hookEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;hookEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;string&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SENTINEL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样后续升级、路径变化、卸载时，都可以只处理插件自己的 hook，尽量不影响用户已有配置。这个地方是写工具类扩展时很容易忽略的：&lt;strong&gt;自动化配置要可回收、可重复执行、不能污染用户配置&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="payload-归一"&gt;&lt;a href="#payload-%e5%bd%92%e4%b8%80" class="header-anchor"&gt;&lt;/a&gt;Payload 归一
&lt;/h2&gt;&lt;p&gt;Claude Code 传给 hook 的数据不应该直接透传到通知层。不同事件的数据结构不一样，通知层也不需要知道所有细节，所以 &lt;code&gt;notify.js&lt;/code&gt; 会先做一次归一：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EVENT_MAP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;PermissionRequest&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;permission_prompt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`🔐 Requesting permission: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool_name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;a tool&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Elicitation&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;elicitation_dialog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;❓ Claude has a question for you&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Stop&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;idle_prompt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;✅ Task completed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;SubagentStop&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;subagent_stop&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;🤖 Subagent finished&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最终写给扩展侧的是统一格式：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;taskTitle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;termBundleId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中 &lt;code&gt;project&lt;/code&gt; 来自 &lt;code&gt;cwd&lt;/code&gt; 的目录名，&lt;code&gt;taskTitle&lt;/code&gt; 会从 transcript 里取第一条用户消息并截断。这样通知里就不是只有一句“任务完成”，而是能看到类似：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;📂 MyBlog · 💬 写一篇 Claude Code Notifier Plus 的文章
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✅ 任务完成
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;项目窗口一多，这个上下文非常重要。否则通知弹出来以后，还要自己回忆“这是哪个任务”，体验就差很多。&lt;/p&gt;
&lt;h2 id="pipemarker-和-lock"&gt;&lt;a href="#pipemarker-%e5%92%8c-lock" class="header-anchor"&gt;&lt;/a&gt;Pipe、Marker 和 Lock
&lt;/h2&gt;&lt;p&gt;扩展和 hook 之间用了三个文件：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CLAUDE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;homedir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;.claude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NOTIFY_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CLAUDE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;notify-plus.pipe&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MARKER_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CLAUDE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;notify-plus.active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;notify-plus.pipe&lt;/code&gt; 不是严格意义上的 Unix pipe，而是一个固定路径的普通文件。hook 把 JSON payload 写进去，扩展用 &lt;code&gt;fs.watch&lt;/code&gt; 监听变化：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fileWatcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NOTIFY_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;handleNotification&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果 &lt;code&gt;fs.watch&lt;/code&gt; 不可用，再 fallback 到 &lt;code&gt;fs.watchFile&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watchFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NOTIFY_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mtimeMs&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mtimeMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;handleNotification&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;notify-plus.active&lt;/code&gt; 用来记录当前活着的 VS Code extension host PID。hook 运行时会检查这个文件，如果发现扩展没有启动，就直接走 CLI fallback 发通知：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;isExtensionActive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pid&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;还有一个 lock 文件：&lt;code&gt;notify-plus.pipe.lock&lt;/code&gt;。多个 VS Code 窗口都可能监听同一个 pipe，如果没有 lock，同一条 payload 可能被多个窗口同时读到并弹多次。所以扩展处理通知前会先抢锁：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;acquireLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lockFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NOTIFY_FILE&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;.lock&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;openSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lockFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;wx&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;closeSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里用的是 &lt;code&gt;wx&lt;/code&gt;，也就是文件不存在时才创建成功。这个方式很朴素，但足够处理“多个扩展进程同时抢同一条通知”的问题。&lt;/p&gt;
&lt;p&gt;另外扩展里还有一个 2 秒窗口的去重：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;isDuplicate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;lastNotifKey&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lastNotifTime&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;lastNotifKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;lastNotifTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Lock 解决跨窗口竞争，dedup 解决短时间内同一事件重复触发。两个点关注的问题不一样，都需要有。&lt;/p&gt;
&lt;h2 id="cli-和-vs-code-的区分"&gt;&lt;a href="#cli-%e5%92%8c-vs-code-%e7%9a%84%e5%8c%ba%e5%88%86" class="header-anchor"&gt;&lt;/a&gt;CLI 和 VS Code 的区分
&lt;/h2&gt;&lt;p&gt;点击通知要跳回哪里？这个问题比“发一条通知”复杂。&lt;/p&gt;
&lt;p&gt;如果 Claude Code 跑在 VS Code 扩展里，点击通知应该回到 VS Code 项目窗口。如果 Claude Code 跑在 iTerm2 或 Warp 里，点击通知应该回到对应终端。最麻烦的是：VS Code 里也可能有 terminal，进程树判断很容易误判。&lt;/p&gt;
&lt;p&gt;所以 hook 里优先检查 &lt;code&gt;VSCODE_PID&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isVSCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VSCODE_PID&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TERM_PROGRAM&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;vscode&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detectedTermBundleId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isVSCode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getTerminalBundleId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果是 VS Code 环境，就不再尝试检测终端，避免把 VS Code 内部 terminal 误认为外部终端。只有纯 CLI 场景，才去看 &lt;code&gt;TERM_PROGRAM&lt;/code&gt;、&lt;code&gt;LC_TERMINAL&lt;/code&gt;，再不行就沿进程树往上找：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^Terminal$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comm&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;com.apple.Terminal&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/iTerm/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comm&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;com.googlecode.iterm2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/[Ww]arp/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comm&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dev.warp.Warp-Stable&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个判断并不是追求覆盖所有终端，而是优先保证主流场景不要跳错。工具类插件里，“不支持某些边缘终端”通常比“跳到错误窗口”更容易接受。&lt;/p&gt;
&lt;h2 id="macos-点击跳转"&gt;&lt;a href="#macos-%e7%82%b9%e5%87%bb%e8%b7%b3%e8%bd%ac" class="header-anchor"&gt;&lt;/a&gt;macOS 点击跳转
&lt;/h2&gt;&lt;p&gt;macOS 普通 &lt;code&gt;osascript display notification&lt;/code&gt; 能发通知，但点击行为不好控制。所以增强模式下使用 &lt;code&gt;terminal-notifier&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;发通知时会带上 &lt;code&gt;-activate&lt;/code&gt; 和 &lt;code&gt;-execute&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;-title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;✨ Claude Code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;-message&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;safe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;-activate&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;activateBundleId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;-group&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;groupId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-execute&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;executeScript&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;removeCmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-activate&lt;/code&gt; 负责激活 App，&lt;code&gt;-execute&lt;/code&gt; 负责进一步执行 AppleScript，把具体窗口 raise 到前台。VS Code 场景下会按窗口标题匹配项目名：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;set&lt;/span&gt; &lt;span class="nx"&gt;wins&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;every&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="nx"&gt;whose&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;MyBlog&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;wins&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nx"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;perform&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;AXRaise&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;wins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个实现依赖辅助功能权限，所以插件第一次启动时会提示用户授权。没有授权也不是完全不可用，只是从“跳到具体项目窗口”降级成“激活 VS Code / 终端 App”。&lt;/p&gt;
&lt;p&gt;另外通知里带了 &lt;code&gt;groupId&lt;/code&gt;，点击后会执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;terminal-notifier -remove &lt;span class="s1"&gt;&amp;#39;claude-notif-...&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样点完通知以后，通知中心里不会一直堆着旧消息。这个细节对高频使用来说挺重要，不然一天跑下来通知中心会很乱。&lt;/p&gt;
&lt;h2 id="选中代码发送到-claude-code"&gt;&lt;a href="#%e9%80%89%e4%b8%ad%e4%bb%a3%e7%a0%81%e5%8f%91%e9%80%81%e5%88%b0-claude-code" class="header-anchor"&gt;&lt;/a&gt;选中代码发送到 Claude Code
&lt;/h2&gt;&lt;p&gt;除了通知之外，插件还加了一个小功能：把选中的代码快速发给 Claude Code。&lt;/p&gt;
&lt;p&gt;入口有几个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;编辑器右上角按钮。&lt;/li&gt;
&lt;li&gt;右键菜单。&lt;/li&gt;
&lt;li&gt;Lightbulb code action。&lt;/li&gt;
&lt;li&gt;快捷键 &lt;code&gt;Cmd+Shift+I&lt;/code&gt; / &lt;code&gt;Ctrl+Shift+I&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;资源管理器里右键某个文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实现上并没有重新发明一套与 Claude Code webview 通信的协议，而是复用 Claude Code 扩展已有的命令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;claude-vscode.insertAtMention&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果 Claude Code 面板还没打开，就先打开最近的 Claude Code，再插入：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;claude-vscode.insertAtMention&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;claude-vscode.editor.openLast&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;claude-vscode.insertAtMention&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个功能看起来和通知没什么关系，但实际工作流里很顺手：选中一段代码，按快捷键，Claude Code 输入框里就会出现 &lt;code&gt;@file#line-range&lt;/code&gt; 引用。它拿到的是文件引用，而不是单纯复制文本，所以后续还能基于完整上下文继续读文件。&lt;/p&gt;
&lt;h2 id="配置项"&gt;&lt;a href="#%e9%85%8d%e7%bd%ae%e9%a1%b9" class="header-anchor"&gt;&lt;/a&gt;配置项
&lt;/h2&gt;&lt;p&gt;目前配置都放在 &lt;code&gt;claudeCodeNotifierPlus.*&lt;/code&gt; 下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;claudeCodeNotifierPlus.notifyOnPermissionRequest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;claudeCodeNotifierPlus.notifyOnQuestion&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;claudeCodeNotifierPlus.notifyOnTaskComplete&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;claudeCodeNotifierPlus.notifyOnSubagentStop&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;claudeCodeNotifierPlus.systemNotification&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;claudeCodeNotifierPlus.sound&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;claudeCodeNotifierPlus.notificationDelay&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;claudeCodeNotifierPlus.showVSCodePopup&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;claudeCodeNotifierPlus.clickToFocus&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;window&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;claudeCodeNotifierPlus.suppressWhenFocused&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里默认不开 &lt;code&gt;subagent_stop&lt;/code&gt;，是因为 subagent 在复杂任务里可能比较频繁，如果每个子任务完成都弹一下，会比较吵。默认只提醒主流程真正需要注意的事件。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;notificationDelay&lt;/code&gt; 这个配置也比较实用。比如你同时打开了 VS Code popup 和系统通知，可以设置几秒延迟；如果你在这几秒里手动点掉 VS Code popup，就取消系统通知，避免重复打扰。&lt;/p&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Claude Code Notifier Plus&lt;/code&gt; 本质上不是一个复杂项目，它解决的是一个很具体的效率问题：让 Claude Code 在需要人介入时把我叫回来，而不是让我一直盯着它。&lt;/p&gt;
&lt;p&gt;这次实现里我自己比较关注几个点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不依赖单一 &lt;code&gt;Notification&lt;/code&gt; hook，而是监听更明确的事件。&lt;/li&gt;
&lt;li&gt;hook 和 VS Code extension host 之间用固定文件通信，简单、可调试、跨环境稳定。&lt;/li&gt;
&lt;li&gt;CLI 和 VS Code 场景分开处理，避免点击通知跳错窗口。&lt;/li&gt;
&lt;li&gt;自动修改用户配置时，用 sentinel 标记自己的内容，做到可重复安装和可清理。&lt;/li&gt;
&lt;li&gt;多窗口场景下用 marker、lock、dedup 处理重复通知。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总的来说，AI Coding 工具越来越强以后，工程师的工作方式也会跟着变化。以前我们关注的是“怎么让工具更聪明”，现在也要关注“怎么让工具在合适的时候打断我”。打断太少会错过确认，打断太多又会变成噪音。这个插件就是在这个小问题上的一次实践。&lt;/p&gt;
&lt;h2 id="参考"&gt;&lt;a href="#%e5%8f%82%e8%80%83" class="header-anchor"&gt;&lt;/a&gt;参考
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/zekunyan/claude-code-notifier-plus" target="_blank" rel="noopener"
 &gt;Claude Code Notifier Plus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://marketplace.visualstudio.com/items?itemName=tutuge.claude-code-notifier-plus" target="_blank" rel="noopener"
 &gt;VS Code Marketplace - Claude Code Notifier Plus&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>九年之后，重新整理一下过去写过的文章</title><link>https://tutuge.me/2026/07/01/myblog-posts-index/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tutuge.me/2026/07/01/myblog-posts-index/</guid><description>&lt;h2 id="前言"&gt;&lt;a href="#%e5%89%8d%e8%a8%80" class="header-anchor"&gt;&lt;/a&gt;前言
&lt;/h2&gt;&lt;p&gt;距离上一次写博客已经过去 9 年了。回头看了一下最后几篇文章的时间，确实有点恍惚，时光荏苒这句话放在这里还挺合适。&lt;/p&gt;
&lt;p&gt;这几年技术变化很快，尤其随着 AI 时代的来临，查资料、整理脉络、校对文字、补齐上下文的成本都低了不少。以前写一篇文章，很多精力会花在资料整理和表达打磨上；现在这些环节可以被工具分担一部分，反而更应该把项目里的问题、判断和思考沉淀下来。&lt;/p&gt;
&lt;p&gt;所以接下来还是想把博客重新写起来，并且尽量不间断地继续更新。在继续写新的内容之前，先把过去几年写过的文章做一次总结和索引，也算是给这个博客重新接上线。&lt;/p&gt;
&lt;p&gt;之前写文章的时候基本都是遇到一个问题、做了一个控件、踩了一个坑，然后顺手整理出来。时间长了以后，单篇文章之间其实是有脉络的：有些是 iOS 工程结构，有些是 Runtime 和内存管理，有些是 UI 交互和开源控件，还有一些是读书笔记、翻译和服务端相关的小实践。本文就把它们按主题重新整理一下，方便以后自己回头查，也方便刚好搜到某一篇的同学顺着同一类问题继续看下去。&lt;/p&gt;
&lt;h2 id="整体回顾"&gt;&lt;a href="#%e6%95%b4%e4%bd%93%e5%9b%9e%e9%a1%be" class="header-anchor"&gt;&lt;/a&gt;整体回顾
&lt;/h2&gt;&lt;p&gt;目前博客里的旧文章主要集中在 2014 到 2017 年，一共 42 篇。现在回头看，这些文章当然带着很明显的时间痕迹：Objective-C、Masonry、CocoaPods、早期 Swift、Runtime、反编译，这些都是当时做 iOS 开发绕不开的话题。&lt;/p&gt;
&lt;p&gt;不过很多问题并没有过时，只是换了技术栈和工具形态。比如代码怎么拆、接口怎么设计、复杂页面怎么优化、遇到奇怪问题怎么验证，这些到现在依然是工程里每天都会遇到的事情。&lt;/p&gt;
&lt;p&gt;大致按年份看：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2014 年&lt;/strong&gt;，主要是刚开始写 Blog，以及一个 Android 开源控件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2015 年&lt;/strong&gt;，文章最多，覆盖了 iOS 项目结构、Objective-C 基础、UITableView 性能、GCD、Auto Layout、Runtime、Swift 控件等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2016 年&lt;/strong&gt;，更多是在总结工程化、模块化、代码组织、API 设计，也有一些更底层的内存管理和消息发送内容。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2017 年&lt;/strong&gt;，主要集中在 Runtime、反编译、Xcode Bug 分析，以及几个控件和工具库。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果按阅读路线来分，则可以这样看：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;想看 iOS 工程实践，可以先看项目结构、模块化、代码组织。&lt;/li&gt;
&lt;li&gt;想看 Objective-C 底层，可以从 &lt;code&gt;@autoreleasepool&lt;/code&gt;、&lt;code&gt;__autoreleasing&lt;/code&gt;、&lt;code&gt;objc_msgSend&lt;/code&gt;、&lt;code&gt;methodSignatureForSelector:&lt;/code&gt; 这几篇开始。&lt;/li&gt;
&lt;li&gt;想看 UI 实现，可以看 Auto Layout 系列、UITextView 图文混排、几个 TTG 开源控件。&lt;/li&gt;
&lt;li&gt;想查具体问题，就直接按下面的主题索引找。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="工程结构架构与协作"&gt;&lt;a href="#%e5%b7%a5%e7%a8%8b%e7%bb%93%e6%9e%84%e6%9e%b6%e6%9e%84%e4%b8%8e%e5%8d%8f%e4%bd%9c" class="header-anchor"&gt;&lt;/a&gt;工程结构、架构与协作
&lt;/h2&gt;&lt;p&gt;这一类文章主要讨论“代码应该怎么放、怎么拆、怎么协作”。很多观点放到现在不一定是唯一答案，但是问题本身一直存在：项目变大以后，如何让代码还能被理解、修改和复用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/02/01/iOS%e9%a1%b9%e7%9b%ae%e7%9a%84%e7%9b%ae%e5%bd%95%e7%bb%93%e6%9e%84-%e5%8e%9f%e5%88%9b/" &gt;iOS项目的目录结构-原创&lt;/a&gt;&lt;br&gt;
比较早的一篇工程结构整理，重点是从目录层面把业务、工具、资源、第三方代码分清楚。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/03/01/Entity%e5%92%8cModel%e7%9a%84%e4%b8%8d%e5%90%8c-%e5%85%b3%e4%ba%8e%e4%bb%a3%e7%a0%81%e7%9a%84%e6%95%b0%e6%8d%ae%e5%b1%82/" &gt;Entity和Model的不同-关于代码的数据层&lt;/a&gt;&lt;br&gt;
讨论数据层对象的职责边界，适合和后面的 ViewModel、数据加工、接口返回设计一起看。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/02/16/%e5%88%a9%e7%94%a8NSProxy%e5%ae%9e%e7%8e%b0%e6%b6%88%e6%81%af%e8%bd%ac%e5%8f%91-%e6%a8%a1%e5%9d%97%e5%8c%96%e7%9a%84%e7%bd%91%e7%bb%9c%e6%8e%a5%e5%8f%a3%e5%b1%82%e8%ae%be%e8%ae%a1-%e5%8e%9f%e5%88%9b/" &gt;利用NSProxy实现消息转发-模块化的网络接口层设计-原创&lt;/a&gt;&lt;br&gt;
从 &lt;code&gt;NSProxy&lt;/code&gt; 和消息转发切入，把网络接口层做成更清晰的调用入口。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2016/03/29/modular-and-component-summary/" &gt;对组件化与模块化的思考与总结&lt;/a&gt;&lt;br&gt;
当时读了不少组件化文章后做的一次整理，重点是“模块”和“组件”的区别，以及如何从业务、技术、界面、数据几条线去拆分。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2016/04/10/summary-of-code-organization/" &gt;总结一些iOS项目中组织代码的方法&lt;/a&gt;&lt;br&gt;
从 CocoaPods 私有库、Carthage、Xcode Group、Category、&lt;code&gt;#pragma mark&lt;/code&gt; 等不同粒度整理代码组织方法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2016/05/02/design-json-api-respoense/" &gt;API返回结果设计经验与总结&lt;/a&gt;&lt;br&gt;
虽然分类放在服务端，但本质也是客户端和服务端协作的接口契约问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/02/28/%e4%b8%80%e6%ac%a1%e5%ae%a1%e6%a0%b8%e8%a2%ab%e6%8b%92%e7%9a%84%e7%bb%8f%e5%8e%86-%e5%85%b3%e4%ba%8eiCloud%e5%88%b0%e5%ba%95%e5%ba%94%e8%af%a5%e5%a4%87%e4%bb%bd%e4%bb%80%e4%b9%88%e6%95%b0%e6%8d%ae/" &gt;一次审核被拒的经历-关于iCloud到底应该备份什么数据&lt;/a&gt;&lt;br&gt;
一个偏实践的审核问题，记录了 iCloud 备份数据边界，属于踩坑型文章。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="objective-cruntime-与内存管理"&gt;&lt;a href="#objective-cruntime-%e4%b8%8e%e5%86%85%e5%ad%98%e7%ae%a1%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;Objective-C、Runtime 与内存管理
&lt;/h2&gt;&lt;p&gt;这一组文章是我自己回头看时比较有连续性的部分：一开始是内存和属性行为，后面慢慢过渡到 Runtime、消息转发、反编译和系统实现。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/02/18/NSString%e7%9a%84Copy%e4%b8%8e%e5%86%85%e5%ad%98%e5%88%86%e9%85%8d/" &gt;NSString的Copy与内存分配&lt;/a&gt;&lt;br&gt;
从 &lt;code&gt;NSString&lt;/code&gt;、&lt;code&gt;NSMutableString&lt;/code&gt; 的 &lt;code&gt;copy&lt;/code&gt; 行为入手，看对象拷贝和内存分配。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/03/17/what-is-autoreleasepool/" &gt;@autoreleasepool-内存的分配与释放&lt;/a&gt;&lt;br&gt;
整理 &lt;code&gt;@autoreleasepool&lt;/code&gt; 的基本行为，是后面看 &lt;code&gt;__autoreleasing&lt;/code&gt; 的基础。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2016/04/30/autoreleasing-meet-autoreleasepool/" &gt;结合访问Out Parameters出现EXC_BAD_ACCESS的例子，反编译汇编解读__autoreleasing&lt;/a&gt;&lt;br&gt;
通过 Out Parameters 的 crash 例子，继续看 &lt;code&gt;__autoreleasing&lt;/code&gt; 和 autorelease pool 的关系。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2016/06/19/translation-why-objcmsgsend-must-be-written-in-assembly/" &gt;翻译-为什么objc_msgSend必须用汇编实现&lt;/a&gt;&lt;br&gt;
一篇翻译文章，但对理解 Objective-C 消息发送、寄存器、调用约定很有帮助。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/07/08/fill-nil-property-of-object/" &gt;用Runtime的手段填充任意NSObject对象的nil属性&lt;/a&gt;&lt;br&gt;
一个用 Runtime 遍历属性并填充默认值的小实践，适合理解属性列表和动态处理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/07/25/compatibility-with-macro-category-and-runtime/" &gt;解决iOS项目的版本兼容问题-结合宏、Category和Runtime&lt;/a&gt;&lt;br&gt;
用宏、Category、Runtime 做版本兼容处理，属于工程实践和 Runtime 的交叉。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2017/03/11/TTGDeallocTaskHelper/" &gt;在对象dealloc的后期执行Task-开源库TTGDeallocTaskHelper&lt;/a&gt;&lt;br&gt;
围绕对象销毁阶段执行任务，涉及生命周期、关联对象和封装方式。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2017/03/14/xcode8-crash-on-out-parameter/" &gt;反编译分析Xcode8的Bug, release下连续两次调用有二级指针参数的空方法会Crash&lt;/a&gt;&lt;br&gt;
一个比较典型的“写 Demo 验证 -&amp;gt; 反编译分析 -&amp;gt; 总结原因”的问题排查过程。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2017/04/08/diy-methodSignatureForSelector/" &gt;反编译分析并模拟实现methodSignatureForSelector方法&lt;/a&gt;&lt;br&gt;
从 &lt;code&gt;methodSignatureForSelector:&lt;/code&gt; 的行为差异出发，最后尝试模拟实现，适合作为 Runtime 深入阅读的一篇。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="并发性能与小技巧"&gt;&lt;a href="#%e5%b9%b6%e5%8f%91%e6%80%a7%e8%83%bd%e4%b8%8e%e5%b0%8f%e6%8a%80%e5%b7%a7" class="header-anchor"&gt;&lt;/a&gt;并发、性能与小技巧
&lt;/h2&gt;&lt;p&gt;这一类文章更偏“项目里马上能用到”的经验。现在很多技术栈已经变化了，但里面的一些判断方式仍然有参考意义，比如提前计算、缓存、减少重复创建、明确线程上下文等。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/02/19/%e6%8f%90%e5%8d%87UITableView%e6%80%a7%e8%83%bd-%e5%a4%8d%e6%9d%82%e9%a1%b5%e9%9d%a2%e7%9a%84%e4%bc%98%e5%8c%96/" &gt;提升UITableView性能-复杂页面的优化&lt;/a&gt;&lt;br&gt;
从复杂动态页面入手，总结 Cell 复用、高度缓存、数据预处理、View 缓存等优化点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/04/03/something-about-gcd/" &gt;GCD使用经验与技巧浅谈&lt;/a&gt;&lt;br&gt;
对 GCD 常见使用方式做了一次整理，包括队列、同步异步、延迟执行等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/03/29/bind-data-to-gcd-queue/" &gt;为GCD队列绑定NSObject类型上下文数据-利用__bridge_retained(transfer)转移内存管理权&lt;/a&gt;&lt;br&gt;
一个更细的问题：如何给 GCD 队列绑定 Objective-C 对象，并正确处理内存管理权。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/02/19/Block%e7%b1%bb%e5%9e%8b%e5%8f%98%e9%87%8f-%e7%bc%93%e5%ad%98Http%e8%af%b7%e6%b1%82%e4%b8%8e%e5%9b%9e%e8%b0%83/" &gt;Block类型变量-缓存Http请求与回调&lt;/a&gt;&lt;br&gt;
通过 Block 保存回调，解决重复请求和回调分发问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/03/15/%e6%9c%89%e5%85%b3%e5%ae%8f%e5%ae%9a%e4%b9%89%e7%9a%84%e7%bb%8f%e9%aa%8c%e4%b8%8e%e6%8a%80%e5%b7%a7-%e7%ae%80%e5%8c%96%e4%bb%a3%e7%a0%81-%e5%a2%9e%e5%bc%baLog/" &gt;有关宏定义的经验与技巧-简化代码-增强Log&lt;/a&gt;&lt;br&gt;
记录了一些宏定义用法，重点是让重复代码更少、调试信息更清楚。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ui布局与交互实现"&gt;&lt;a href="#ui%e5%b8%83%e5%b1%80%e4%b8%8e%e4%ba%a4%e4%ba%92%e5%ae%9e%e7%8e%b0" class="header-anchor"&gt;&lt;/a&gt;UI、布局与交互实现
&lt;/h2&gt;&lt;p&gt;这一组基本都是“先有一个效果，再拆解实现”的文章。相比纯理论，我自己更喜欢这种写法：先把画面、交互、约束关系摆出来，再一点点推到代码。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/03/07/UITextView%e7%bc%96%e8%be%91%e6%97%b6%e6%8f%92%e5%85%a5%e8%87%aa%e5%ae%9a%e4%b9%89%e8%a1%a8%e6%83%85-%e7%ae%80%e5%8d%95%e7%9a%84%e5%9b%be%e6%96%87%e6%b7%b7%e7%bc%96/" &gt;UITextView编辑时插入自定义表情-简单的图文混编&lt;/a&gt;&lt;br&gt;
记录 &lt;code&gt;UITextView&lt;/code&gt; 中插入自定义表情的基础实现。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/04/21/resizable-nstextattachment/" &gt;UITextView编辑时插入自定义表情-续-自定义表情图片的大小&lt;/a&gt;&lt;br&gt;
继续处理 &lt;code&gt;NSTextAttachment&lt;/code&gt; 图片大小的问题，是上一篇的补充。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/05/23/autolayout-example-with-masonry/" &gt;有趣的Autolayout示例-Masonry实现&lt;/a&gt;&lt;br&gt;
Auto Layout + Masonry 系列第一篇，用有趣的布局示例解释约束。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/08/08/autolayout-example-with-masonry2/" &gt;有趣的Autolayout示例2-Masonry实现&lt;/a&gt;&lt;br&gt;
Auto Layout 系列第二篇，继续通过示例练习约束关系。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/12/14/autolayout-example-with-masonry3/" &gt;有趣的Autolayout示例3-Masonry实现&lt;/a&gt;&lt;br&gt;
Auto Layout 系列第三篇。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2016/08/06/autolayout-example-with-masonry4/" &gt;有趣的Autolayout示例4-Masonry实现&lt;/a&gt;&lt;br&gt;
Auto Layout 系列第四篇。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2017/03/12/autolayout-example-with-masonry5/" &gt;有趣的Autolayout示例5-Masonry实现&lt;/a&gt;&lt;br&gt;
Auto Layout 系列第五篇，也是这个系列后面的一次补充。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2017/03/08/quartzcode-demo-star-animation/" &gt;用QuartzCode快速实现一个收藏动画&lt;/a&gt;&lt;br&gt;
通过 QuartzCode 快速做收藏动画，重点是工具生成动画代码后的理解和使用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="开源项目与控件实现"&gt;&lt;a href="#%e5%bc%80%e6%ba%90%e9%a1%b9%e7%9b%ae%e4%b8%8e%e6%8e%a7%e4%bb%b6%e5%ae%9e%e7%8e%b0" class="header-anchor"&gt;&lt;/a&gt;开源项目与控件实现
&lt;/h2&gt;&lt;p&gt;这些文章基本都围绕一个具体控件或工具展开：为什么做、最终效果是什么、API 怎么设计、关键实现点在哪里。现在看起来，有些库可能已经偏老了，不过当时做开源项目时对封装、文档、Demo 的训练还是很有价值。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2014/11/02/LinkTextView-Android%e5%bc%80%e6%ba%90%e5%ba%93-%e5%8e%9f%e5%88%9b/" &gt;Android开源库-LinkTextView-原创&lt;/a&gt;&lt;br&gt;
早期的 Android 开源控件，用来处理文本中的链接点击。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/10/09/ttgsnackbar-lib/" &gt;Swift写的库-TTGSnackbar&lt;/a&gt;&lt;br&gt;
一个 Swift 写的 Snackbar 控件，重点在交互展示和 API 封装。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/10/25/ttgemojirate-lib/" &gt;Swift开源项目: TTGEmojiRate的实现&lt;/a&gt;&lt;br&gt;
基于 &lt;code&gt;UIBezierPath&lt;/code&gt;、手势和绘制实现 Emoji 评分控件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/12/31/TTGTagCollectionView/" &gt;开源项目-TTGTagCollectionView&lt;/a&gt;&lt;br&gt;
标签布局控件，适合看布局计算、数据源和代理 API 的设计。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2016/08/28/TTGBingWallPaper/" &gt;Swift开源Mac App - BingWallPaper&lt;/a&gt;&lt;br&gt;
一个 macOS 菜单栏应用，用 Swift 获取 Bing 壁纸并设置桌面背景。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2016/12/25/TTGPuzzleVerify/" &gt;开源项目-拼图验证控件TTGPuzzleVerify的实现&lt;/a&gt;&lt;br&gt;
拼图验证控件，包含图片裁剪、遮罩、滑动验证等实现点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2017/03/11/TTGDeallocTaskHelper/" &gt;在对象dealloc的后期执行Task-开源库TTGDeallocTaskHelper&lt;/a&gt;&lt;br&gt;
既是开源库，也可以归到 Runtime 和对象生命周期相关内容里。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="读书笔记与翻译"&gt;&lt;a href="#%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0%e4%b8%8e%e7%bf%bb%e8%af%91" class="header-anchor"&gt;&lt;/a&gt;读书笔记与翻译
&lt;/h2&gt;&lt;p&gt;这部分主要是阅读《Effective Objective-C》和技术文章后的整理。它们不算完整翻译，更像是把书里的点和自己的理解合在一起做记录。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/02/05/Effective-Objective-C-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-Item-1/" &gt;Effective-Objective-C-读书笔记-Item-1&lt;/a&gt;&lt;br&gt;
Effective Objective-C 系列笔记开头。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/02/06/Effective-Objective-C-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-Item-2/" &gt;Effective-Objective-C-读书笔记-Item-2&lt;/a&gt;&lt;br&gt;
继续整理 Objective-C 基础概念。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/02/14/Effective-Objective-C-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-Item-3/" &gt;Effective-Objective-C-读书笔记-Item-3&lt;/a&gt;&lt;br&gt;
读书笔记第三篇。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/03/11/Effective-Objective-C-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-Item-4-%e5%a6%82%e4%bd%95%e6%ad%a3%e7%a1%ae%e5%ae%9a%e4%b9%89%e5%b8%b8%e9%87%8f/" &gt;Effective-Objective-C-读书笔记-Item-4-如何正确定义常量&lt;/a&gt;&lt;br&gt;
重点是常量定义方式，也是实际项目里很常见的问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/03/21/effective-objective-c-5-enum/" &gt;Enum-枚举的正确使用-Effective-Objective-C-读书笔记-Item-5&lt;/a&gt;&lt;br&gt;
整理枚举的正确使用方式，包括位掩码等内容。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2016/06/19/translation-why-objcmsgsend-must-be-written-in-assembly/" &gt;翻译-为什么objc_msgSend必须用汇编实现&lt;/a&gt;&lt;br&gt;
这篇同时也属于 Runtime 主题，放在这里是因为它本身是翻译文章。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="服务端跨端与其它"&gt;&lt;a href="#%e6%9c%8d%e5%8a%a1%e7%ab%af%e8%b7%a8%e7%ab%af%e4%b8%8e%e5%85%b6%e5%ae%83" class="header-anchor"&gt;&lt;/a&gt;服务端、跨端与其它
&lt;/h2&gt;&lt;p&gt;虽然博客大部分是 iOS，但也有几篇不是纯 iOS 的内容。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2015/04/19/thrift-example-cpp-and-php/" &gt;RPC框架Thrift例子-PHP调用C++后端程序&lt;/a&gt;&lt;br&gt;
用一个小例子记录 Thrift、PHP、C++ 后端之间的调用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2016/05/02/design-json-api-respoense/" &gt;API返回结果设计经验与总结&lt;/a&gt;&lt;br&gt;
从接口返回结构出发，讨论 &lt;code&gt;code&lt;/code&gt;、&lt;code&gt;message&lt;/code&gt;、&lt;code&gt;data&lt;/code&gt; 等字段的设计。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2014/11/02/LinkTextView-Android%e5%bc%80%e6%ba%90%e5%ba%93-%e5%8e%9f%e5%88%9b/" &gt;Android开源库-LinkTextView-原创&lt;/a&gt;&lt;br&gt;
Android 控件文章，也算是早期跨端尝试的一部分。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://tutuge.me/2014/11/01/%e7%ac%ac%e4%b8%80%e7%af%87Blog/" &gt;第一篇Blog&lt;/a&gt;&lt;br&gt;
真正意义上的第一篇，更多是一个开始的记录。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="按年份索引"&gt;&lt;a href="#%e6%8c%89%e5%b9%b4%e4%bb%bd%e7%b4%a2%e5%bc%95" class="header-anchor"&gt;&lt;/a&gt;按年份索引
&lt;/h2&gt;&lt;p&gt;如果不按主题，而是按时间看，脉络会更明显一些。&lt;/p&gt;
&lt;h3 id="2014"&gt;&lt;a href="#2014" class="header-anchor"&gt;&lt;/a&gt;2014
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2014/11/01/%e7%ac%ac%e4%b8%80%e7%af%87Blog/" &gt;第一篇Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2014/11/02/LinkTextView-Android%e5%bc%80%e6%ba%90%e5%ba%93-%e5%8e%9f%e5%88%9b/" &gt;Android开源库-LinkTextView-原创&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2015"&gt;&lt;a href="#2015" class="header-anchor"&gt;&lt;/a&gt;2015
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/02/01/iOS%e9%a1%b9%e7%9b%ae%e7%9a%84%e7%9b%ae%e5%bd%95%e7%bb%93%e6%9e%84-%e5%8e%9f%e5%88%9b/" &gt;iOS项目的目录结构-原创&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/02/05/Effective-Objective-C-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-Item-1/" &gt;Effective-Objective-C-读书笔记-Item-1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/02/06/Effective-Objective-C-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-Item-2/" &gt;Effective-Objective-C-读书笔记-Item-2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/02/14/Effective-Objective-C-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-Item-3/" &gt;Effective-Objective-C-读书笔记-Item-3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/02/16/%e5%88%a9%e7%94%a8NSProxy%e5%ae%9e%e7%8e%b0%e6%b6%88%e6%81%af%e8%bd%ac%e5%8f%91-%e6%a8%a1%e5%9d%97%e5%8c%96%e7%9a%84%e7%bd%91%e7%bb%9c%e6%8e%a5%e5%8f%a3%e5%b1%82%e8%ae%be%e8%ae%a1-%e5%8e%9f%e5%88%9b/" &gt;利用NSProxy实现消息转发-模块化的网络接口层设计-原创&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/02/18/NSString%e7%9a%84Copy%e4%b8%8e%e5%86%85%e5%ad%98%e5%88%86%e9%85%8d/" &gt;NSString的Copy与内存分配&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/02/19/%e6%8f%90%e5%8d%87UITableView%e6%80%a7%e8%83%bd-%e5%a4%8d%e6%9d%82%e9%a1%b5%e9%9d%a2%e7%9a%84%e4%bc%98%e5%8c%96/" &gt;提升UITableView性能-复杂页面的优化&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/02/19/Block%e7%b1%bb%e5%9e%8b%e5%8f%98%e9%87%8f-%e7%bc%93%e5%ad%98Http%e8%af%b7%e6%b1%82%e4%b8%8e%e5%9b%9e%e8%b0%83/" &gt;Block类型变量-缓存Http请求与回调&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/02/28/%e4%b8%80%e6%ac%a1%e5%ae%a1%e6%a0%b8%e8%a2%ab%e6%8b%92%e7%9a%84%e7%bb%8f%e5%8e%86-%e5%85%b3%e4%ba%8eiCloud%e5%88%b0%e5%ba%95%e5%ba%94%e8%af%a5%e5%a4%87%e4%bb%bd%e4%bb%80%e4%b9%88%e6%95%b0%e6%8d%ae/" &gt;一次审核被拒的经历-关于iCloud到底应该备份什么数据&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/03/01/Entity%e5%92%8cModel%e7%9a%84%e4%b8%8d%e5%90%8c-%e5%85%b3%e4%ba%8e%e4%bb%a3%e7%a0%81%e7%9a%84%e6%95%b0%e6%8d%ae%e5%b1%82/" &gt;Entity和Model的不同-关于代码的数据层&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/03/07/UITextView%e7%bc%96%e8%be%91%e6%97%b6%e6%8f%92%e5%85%a5%e8%87%aa%e5%ae%9a%e4%b9%89%e8%a1%a8%e6%83%85-%e7%ae%80%e5%8d%95%e7%9a%84%e5%9b%be%e6%96%87%e6%b7%b7%e7%bc%96/" &gt;UITextView编辑时插入自定义表情-简单的图文混编&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/03/11/Effective-Objective-C-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-Item-4-%e5%a6%82%e4%bd%95%e6%ad%a3%e7%a1%ae%e5%ae%9a%e4%b9%89%e5%b8%b8%e9%87%8f/" &gt;Effective-Objective-C-读书笔记-Item-4-如何正确定义常量&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/03/15/%e6%9c%89%e5%85%b3%e5%ae%8f%e5%ae%9a%e4%b9%89%e7%9a%84%e7%bb%8f%e9%aa%8c%e4%b8%8e%e6%8a%80%e5%b7%a7-%e7%ae%80%e5%8c%96%e4%bb%a3%e7%a0%81-%e5%a2%9e%e5%bc%baLog/" &gt;有关宏定义的经验与技巧-简化代码-增强Log&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/03/17/what-is-autoreleasepool/" &gt;@autoreleasepool-内存的分配与释放&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/03/21/effective-objective-c-5-enum/" &gt;Enum-枚举的正确使用-Effective-Objective-C-读书笔记-Item-5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/03/29/bind-data-to-gcd-queue/" &gt;为GCD队列绑定NSObject类型上下文数据-利用__bridge_retained(transfer)转移内存管理权&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/04/03/something-about-gcd/" &gt;GCD使用经验与技巧浅谈&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/04/19/thrift-example-cpp-and-php/" &gt;RPC框架Thrift例子-PHP调用C++后端程序&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/04/21/resizable-nstextattachment/" &gt;UITextView编辑时插入自定义表情-续-自定义表情图片的大小&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/05/23/autolayout-example-with-masonry/" &gt;有趣的Autolayout示例-Masonry实现&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/07/08/fill-nil-property-of-object/" &gt;用Runtime的手段填充任意NSObject对象的nil属性&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/07/25/compatibility-with-macro-category-and-runtime/" &gt;解决iOS项目的版本兼容问题-结合宏、Category和Runtime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/08/08/autolayout-example-with-masonry2/" &gt;有趣的Autolayout示例2-Masonry实现&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/10/09/ttgsnackbar-lib/" &gt;Swift写的库-TTGSnackbar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/10/25/ttgemojirate-lib/" &gt;Swift开源项目: TTGEmojiRate的实现&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/12/14/autolayout-example-with-masonry3/" &gt;有趣的Autolayout示例3-Masonry实现&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2015/12/31/TTGTagCollectionView/" &gt;开源项目-TTGTagCollectionView&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2016"&gt;&lt;a href="#2016" class="header-anchor"&gt;&lt;/a&gt;2016
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2016/03/29/modular-and-component-summary/" &gt;对组件化与模块化的思考与总结&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2016/04/10/summary-of-code-organization/" &gt;总结一些iOS项目中组织代码的方法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2016/04/30/autoreleasing-meet-autoreleasepool/" &gt;结合访问Out Parameters出现EXC_BAD_ACCESS的例子，反编译汇编解读__autoreleasing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2016/05/02/design-json-api-respoense/" &gt;API返回结果设计经验与总结&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2016/06/19/translation-why-objcmsgsend-must-be-written-in-assembly/" &gt;翻译-为什么objc_msgSend必须用汇编实现&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2016/08/06/autolayout-example-with-masonry4/" &gt;有趣的Autolayout示例4-Masonry实现&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2016/08/28/TTGBingWallPaper/" &gt;Swift开源Mac App - BingWallPaper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2016/12/25/TTGPuzzleVerify/" &gt;开源项目-拼图验证控件TTGPuzzleVerify的实现&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2017"&gt;&lt;a href="#2017" class="header-anchor"&gt;&lt;/a&gt;2017
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2017/03/08/quartzcode-demo-star-animation/" &gt;用QuartzCode快速实现一个收藏动画&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2017/03/11/TTGDeallocTaskHelper/" &gt;在对象dealloc的后期执行Task-开源库TTGDeallocTaskHelper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2017/03/12/autolayout-example-with-masonry5/" &gt;有趣的Autolayout示例5-Masonry实现&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2017/03/14/xcode8-crash-on-out-parameter/" &gt;反编译分析Xcode8的Bug, release下连续两次调用有二级指针参数的空方法会Crash&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tutuge.me/2017/04/08/diy-methodSignatureForSelector/" &gt;反编译分析并模拟实现methodSignatureForSelector方法&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h2&gt;&lt;p&gt;这次整理之后再看，过去这些文章其实大致围绕几个关键词：&lt;strong&gt;工程结构、Objective-C 底层、UI 实现、性能优化、开源封装&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;单篇文章写的时候往往只是解决一个具体问题，但放在一起看，就能看到当时学习和做项目的路径：先解决页面和业务问题，再关注代码组织和模块拆分，最后慢慢深入到 Runtime、内存管理和反编译验证。&lt;/p&gt;
&lt;p&gt;隔了 9 年再重新开始写博客，感觉像是把一个很久没打开的项目重新拉起来。代码可以重新编译，文章也可以继续更新。AI 工具会让整理和表达的成本更低，但真正值得记录的，还是项目里遇到的问题、做判断时的取舍，以及过一段时间后依然能回头看的经验。&lt;/p&gt;
&lt;p&gt;后面新的文章也会沿着这个索引继续补充。这样博客就不只是按时间堆在一起，而是能变成一份可以回头查、也可以顺着阅读的技术笔记了。&lt;/p&gt;</description></item><item><title>第一篇Blog</title><link>https://tutuge.me/2014/11/01/%E7%AC%AC%E4%B8%80%E7%AF%87Blog/</link><pubDate>Fri, 31 Oct 2014 16:13:15 +0000</pubDate><guid>https://tutuge.me/2014/11/01/%E7%AC%AC%E4%B8%80%E7%AF%87Blog/</guid><description>&lt;p&gt;拖了很久，终于开始写博客了。
一直觉得自己的知识储备不够，一直都在学习，一直都在积累。（其实就是懒好吗=。=）&lt;/p&gt;
&lt;p&gt;说点啥呢，想到啥就说啥吧~&lt;/p&gt;
&lt;p&gt;从小就喜欢模型、乐高积木，喜欢拆东西，印象最深的就是把家里的高级录音机拆了，装不回去，塞到了床底下。。。&lt;/p&gt;
&lt;p&gt;上了学之后，又开始捣鼓四驱车，自己做马达，绕线圈，家里零件一大堆，串联一堆电池，组装遥控车，唯一的遗憾，就是没有做成遥控飞机（没钱=、=）。&lt;/p&gt;
&lt;p&gt;接着，家里有了电脑！！！这要感谢老爹，当时花了一万多，液晶显示器，刚上市的奔腾4啊！&lt;/p&gt;
&lt;p&gt;于是，我又开始拆电脑。。。换显卡、超频，跑分，再超频，再跑分，3DMark就是俺的祖宗啊~&lt;/p&gt;
&lt;p&gt;终于，走上了&lt;strong&gt;编程&lt;/strong&gt;这条“&lt;strong&gt;不归路&lt;/strong&gt;”，义无反顾，一直到了现在。&lt;/p&gt;
&lt;p&gt;用Dev-C++折腾C语言，数据结构、算法，接着又是C++，Window API，QT（这个真觉得不错），MFC（软件课设），Java，Python，Android（上线了几个小应用，拿了Google全国大学生Android应用开发大赛华中地区三等奖），iOS（上线应用，青桔音乐），Linux，还有各种MySQL、MongoDB等数据库的纠结，看各种博客。。。&lt;/p&gt;
&lt;p&gt;每天看的很多，想的也不少，但是终究不够精通，路还很长啊~开写吧～～～～～～&lt;/p&gt;</description></item></channel></rss>