Apple的设备端模型在聊天方面表现糟糕,但在结构化输出和工具调用方面表现出色

我花了几周时间对Apple的设备端模型进行压力测试——这是一个约3B参数的模型,运行在任何Apple Silicon Mac的Neural Engine上。为了全面测试,我构建了Think Local,这是一个macOS应用,可以测试模型的每项功能:聊天、图像生成、结构化输出、工具调用和参数比较。 我的结论:作为聊天机器人,这个模型表现糟糕。但作为结构化输出和工具调用引擎,它出人意料地优秀。 这种区别很重要,因为它完全改变了你应该如何使用这个模型。 聊天表现令人失望——但这没关系 Apple的模型上下文窗口只有4,096个token。对比一下:Claude有100万个token,GPT-4o有128K。使用Apple的模型时,你放入200个token的系统提示,150个token的模式定义和三轮对话,就已经用了70%。 自由文本的质量也不令人印象深刻。回答正确但通用,长会话中重复性强,安全防护机制经常出现令人困扰的误报——问它网络安全相关问题,有时会拒绝回答,因为它对"攻击"这个词理解过于字面化。 如果你把它当聊天机器人来评估,它就是一个平庸的模型。但这样评估就像批评螺丝刀不是好锤子一样。 闪光点:约束解码 这里一切都不同了。Foundation Models支持@Generable——一个将Swift结构体转换为模式的宏,模型必须遵守这个模式。这不是"请求JSON然后祈祷"。这是约束解码:在生成过程中,模型字面上无法输出违反模式的token。 @Generable struct BugReport { @Guide(.anyOf(["bug", "feature", "task"])) var type: String @Guide(.anyOf(["low", "medium", "high", "critical"])) var priority: String var title: String var description: String } 使用这个模式,你告诉它"分类:应用在旋转设备时崩溃",它会返回: { "type": "bug", "priority": "high", "title": "Crash on device rotation", "description": "The application crashes when the user rotates..." } 受限字段(type、priority)始终有效——它不能编造列表之外的值。自由字段(title、description)连贯且简洁。我用几十种不同的输入进行了测试:在200多次执行中,模式错误为零。 在这个模型中,自由生成和约束解码之间的差异是巨大的。这就像让初级开发者随意写作与给他一个有定义字段的表单之间的区别。表单总是获胜。 工具调用:一个知道自己不知道什么的3B模型 这是最让我惊讶的。定义一个工具get_weather(city: String),问它"马德里天气如何?",它会生成一个带有正确参数的结构化调用。问它"法国的首都是什么?",它会直接回答——它知道不需要工具。 一个3B参数的模型,在你的笔记本电脑上运行,无需网络,能区分何时使用工具,何时不用。它不完美——对于模糊的提示有时会困惑——但在明确输入上的准确率很显著。 Apple的工具调用使用相同的约束解码机制:调用是一个@Generable结构体,所以参数始终有效。没有损坏的JSON解析,也没有编造的参数。 Image Playground:没人提到的另一个模型 Apple Intelligence不仅仅是文本。Image Playground是一个独立的框架,在设备上生成三种风格的图像:动画、插图和素描。它也完全在Neural Engine上运行,无需网络。 模式重复:对于它设计的用途,它工作得很好。图标、贴纸、简单构图——结果出人意料地好。图像中的文本、详细的人体解剖学、复杂构图——灾难性的。 Think Local包含一个图像工作室,你可以在其中测试提示并并排比较风格。通过十分钟的提示测试获得的直觉是任何基准测试都无法提供的。 数据 在Apple Silicon Mac上测量,使用Think Local的资源监视器: 指标 值 生成速度 ~40 tok/s 冷启动(无预热) ~800ms 冷启动(有预热) ~200ms 模型内存 ~1.2 GB 上下文窗口 4,096 tokens 参数 ~3B 电池影响 最小(Neural Engine) 电池数据很重要:Neural Engine在推理方面的消耗明显少于CPU。在生成过程中,CPU因为数据封装和UI略有上升,但繁重的工作交给Neural Engine。这使得在后台使用模型进行工具开发成为可能——git钩子、代码检查器、分类器——而不会耗尽笔记本电池。 ...

2026年4月7日 · Fernando

你的 Mac 自带一款免费的 LLM,你可能还没用上

你每个月可能花费 20 到 200 美元来访问 LLM(大型语言模型)。Claude、GPT、Gemini,无论哪一个都在烧你的钱。而这些模型从你的脚本和开发工具调用的大多数场景都可归结如下几类: “把这个错误分类到这五个类别之一。” “给这个变量起个名字。” “告诉我这个 commit 是 fix、feat 还是 refactor。” “用两句话总结这段文字。” 与此同时,你的 Apple Silicon Mac 上还坐着一个拥有 30 亿参数的语言模型:全系统集成、无需支付费用、无需网络连接、无需 API 密钥、甚至不需要网络延迟。而你大概根本没用过。 基础模型框架(Foundation Models Framework) 自 macOS 26 (Tahoe) 起,Apple 推出了 Foundation Models Framework,允许访问 Apple Intelligence 支持的语言模型。这是一个原生的 Swift 框架,适用于 macOS 26、iOS 26 和 iPadOS 26,兼容所有支持 Apple Intelligence 的 Apple Silicon 设备。 值得注意的不仅仅是它免费——这的确很棒,更特别的是它生成 Swift 类型化输出。它不会返回需要你用正则表达式和上帝保佑才能解析的 String,而是直接输出一个结构体(struct)。 import FoundationModels @Generable struct CommitClassification { @Guide(description: "The type of change") @Guide(.anyOf(["fix", "feat", "refactor", "test", "docs", "chore"])) let type: String @Guide(description: "One-line summary of the change, max 72 chars") let summary: String } --- `@Generable` 宏告诉框架在编译时生成架构(schema)。语言模型利用这个架构生成结构化输出。`@Guide` 则限制了可能的值——通俗地说,你给模型铺好了轨道,它就不会出轨。 以下是用法示例: ```swift let session = LanguageModelSession(instructions: """ You are a commit message classifier. Given a git diff, classify the change and write a summary. """) let diff = "..." // 你的 git diff 数据 let result = try await session.respond( to: "Classify this diff:\n\(diff)", generating: CommitClassification.self ) print("\(result.type): \(result.summary)") // "fix: handle nil response in auth flow" 就是这样。没有 URLSession(网络请求),也没有 API 密钥,也不需要解析 JSON,不需要 try? JSONDecoder().decode(SomeType.self, from: data)。整个模型在设备内部运行,依托于你 Mac 的 Neural Engine(神经引擎),并返回 Swift 类型数据,编译器将会为它进行类型检查。 ...

2026年4月4日 · Fernando

NLTagger与情感分析:为什么Apple认为你的代码很消极

想象一下,你正在构建一个分析开发团队对话的应用程序。你希望检测团队的基调是否健康,或者是否存在压力的迹象。你决定使用Apple的NLTagger,因为它本来就有、免费、支持设备端运行,并且不需要服务器。三行代码搞定,开箱即用。 第一惊喜:句子“kill the process and restart the daemon”(杀掉进程并重启守护进程)的得分是**-0.6**。负面,几乎可以说是敌意满满。接着,“Fatal error in memory allocation”(内存分配出现致命错误)的得分是**-0.8**。而“crash report uploaded successfully”(崩溃报告成功上传)——这是字面上的好消息——得分却是**-0.4**。 你的团队健康状态检测工具刚刚判断出你的开发人员处于情绪崩溃的边缘。而实际上他们只是重启了一个服务而已。 问题所在:为另一个世界设计的模型 NLTagger的.sentimentScore方法会返回一个介于-1.0(非常负面)到1.0(非常正面)之间的值。Apple在iOS 13 / macOS 10.15中引入了它,它依赖于系统集成的设备端模型,无法个性化配置,也无法查看其训练数据。 简而言之:这是一个黑盒子,它给你一个数字并希望你信任它。 对于它最初设计的用途来说,它的表现是相当不错的:例如产品评论、社交媒体评论,以及消费领域的文本,在这些场景中,“horrible”意味着糟糕,“fantastic”意味着极好。这是它的领域。而当你将它应用到其他领域时,问题就出现了。 实验:技术文本 vs. 日常文本 我们来测试一下。这是纯Swift代码,可以直接运行在Playground中: import NaturalLanguage func sentiment(of text: String) -> Double { let tagger = NLTagger(tagSchemes: [.sentimentScore]) tagger.string = text let (tag, _) = tagger.tag( at: text.startIndex, unit: .paragraph, scheme: .sentimentScore ) return Double(tag?.rawValue ?? "0") ?? 0 } --- 现在我们输入一些程序员在日常会说的话: | 句子 | 得分范围 | 实际情感 | | ---------------------------------------- | ----------- | ------------------------------------- | | "Kill the background process" | -0.5 ~ -0.7 | 中性(技术指令) | | "Fatal error: index out of range" | -0.7 ~ -0.9 | 中性(编译器消息) | | "Abort the deployment" | -0.4 ~ -0.6 | 中性(操作性决策) | | "Destroy the old database" | -0.5 ~ -0.7 | 中性(维护操作) | | "The crash was caused by a null pointer" | -0.5 ~ -0.8 | 中性(问题诊断) | | "Successfully deployed to production" | +0.3 ~ +0.5 | 正面 | 注意:这里有六个看似正常的技术场景句子,其中五个都被判断为负面。唯一的正面例句是包含术语“successfully”的,模型将其识别为消费评价中的术语。 再来看看模型在它被训练的日常领域中的表现: | 句子 | 得分范围 | 实际情感 | | ------------------------------------ | ----------- | ------------------- | | "This product is terrible" | -0.7 ~ -0.9 | 负面(正确) | | "I love this app, it's amazing" | +0.7 ~ +0.9 | 正面(正确) | | "The food was okay, nothing special" | -0.1 ~ +0.1 | 中性(正确) | 这组句子全都判断得非常准确。实际上,模型本身问题不大。问题在于,它被放在了错误的上下文中。 ## 问题根源:词汇偏见 `NLTagger`的模型并不能理解语义。它并不知道“kill a process”是一个技术隐喻,而“kill a person”是暴力行为。对它来说,“kill”就是一个带负面意味的单词,仅此而已。 这种现象被称为“词汇偏见”(_lexical bias_)——模型基于单个单词(或短语)的极性打分,而没有理解上下文。这就好比一个翻译软件将 "I'm dying of laughter" 翻译成了真正的紧急医疗状况。 技术语言充满了在日常语言中很负面的词: - **kill**、**terminate**、**abort**、**destroy**——常见于进程操作 - **fatal**、**critical**、**severe**——日志级别 - **crash**、**panic**、**fault**——系统事件 - **dead**、**zombie**、**orphan**——进程状态 - **reject**、**deny**、**block**——访问控制 - **break**、**interrupt**、**suspend**——流控制 任何使用这些词的段落——比如错误日志、事后分析报告、拉取请求中的讨论——都会被预测为负面情绪,好像有人正写着遗书。 ## 增加上下文能解决问题吗? 你可能会想:“如果提供更长的段落,模型应该会更好地理解上下文。”试试这个: ```swift let technical = """ The daemon was killed after a fatal memory error. \ We restarted the service and the crash did not recur. \ Deployment completed successfully with zero downtime. """ print(sentiment(of: technical)) // 典型结果:-0.3 ~ -0.5 这是一个描述成功解决问题的段落。结尾显然是积极的。但其中的“killed”、 “fatal”、 “error” 和“crash”这些词的负面权重,远远超过了“successfully”和“completed”的正面积极权重。模型仅仅是将词汇的极性进行平均,而无法理解叙述的含义。 ...

2026年3月28日 · Fernando

Mole:那款你不知道自己需要的 Mac 清理工具(但只有两个命令值得用)

你的 Mac 现在有多少垃圾文件,是你完全不知道的? 我不是指那些2019年烧烤派对的重复照片,也不是那个简直快变成垃圾堆的下载文件夹。我说的是构建的临时文件,比如那些你疫情后就再也没碰过的 node_modules 文件夹、你甚至不记得安装过的框架缓存文件、堆积如山的 Xcode 的 derived data(派生数据),就像床底下的灰尘一样。 我好几个月磁盘空间占用达到了85%,每次删东西都跟玩杂技一样小心翼翼。直到我发现了Mole,运行了 mo purge --dry-run,屏幕上弹出了一个数字:17GB可回收空间。整理出来了三百五十个被遗忘的构建临时文件,还在那里静静地腐烂着。 十七个G!完全没有碰到任何照片或文档。 什么是Mole(以及它不是什么) Mole 是一个适用于 macOS 的CLI工具,它声称可以“深度清理并优化你的 Mac”。它带有一些看起来像瑞士军刀一样多功能的命令菜单: mo clean # 清理缓存、日志和临时文件 mo uninstall # 完全卸载应用程序 mo optimize # 检查并“优化”系统 mo analyze # 分析磁盘使用情况 mo status # 系统健康监控 mo purge # 删除项目构建临时文件 mo installer # 找到旧的安装包 mo touchid # 配置 sudo 使用 Touch ID 八个命令。直接告诉你吧:**只有两个命令值得你花费时间**。其余的从“嗯,这个我自己就搞定了”到“绝对不可能执行”都有。 ## mo purge:低调的宝藏 如果你是开发者,并且用 Mac 已经超过六个月,那么`mo purge`会帮你发现隐藏的宝藏。简单来说,它会扫描你的项目目录,找出那些占用空间却对你完全没有帮助的构建临时文件。 都有哪些类型的临时文件?基本上都是常见的“嫌疑人”: - **node_modules**(你几个月没碰的 Node/Bun 项目) - **target/**(Rust 项目) - **build/** 和 **.build/**(Swift/Xcode 项目) - **DerivedData**(Xcode 的派生数据) - **__pycache__** 和 Python 的虚拟环境 - **.gradle** 和 **build/**(Java/Kotlin 项目) - **Pods/**(iOS 的 CocoaPods 项目) 这些文件夹的好处是:可以100%重新生成。如果你某天重新打开这个项目,运行一个`npm install`或者`cargo build`,它们会从零重新创建。但在此之前,这些文件像“赖账房客”一样,白占着你的磁盘空间不交房租。 ### 正确的操作流程 首先,一切都从**干跑模式(dry-run)**开始: ```bash mo purge --dry-run 这个命令会显示找到的内容以及可以释放的空间大小,但不会实际删除任何内容。你需要利用这个阶段检查列表,并确定它不会误删掉你正在使用的项目文件(比如你当前有几个窗口正在打开一个项目的node_modules)。 ...

2026年3月23日 · Fernando

使用卡尔曼滤波器减少服务器请求(或关于过度设计的愉悦)

我有一个菜单栏应用,需要知道一个数字。一个从 0 到 100 的百分比。为了得到这个数字,我必须每30秒访问一次服务器。 我们来算一笔账:30秒一次请求等于1分钟2次请求,1小时120次,一天工作的8小时里就是960次。每天为了获取一个有时候20分钟都不变的数字发送将近1000个HTTP请求…… 这根本不是监控!简直就是对服务器的骚扰。 真正的问题其实不是技术问题,而是政治问题。 当你依赖一个你无法控制的API时——它既不是公开的,也没有明确的速率限制文件,同时属于一家随时可能变更服务条款的公司——每一条不必要的请求都带着风险。这风险不仅是超时的风险,还有被切断访问权限的风险。 我用的服务端 endpoint 并没有公开的文档支持。它目前是可用的,已经运行了几个月了。但我知道,我发出的每一条请求,都会增加Anthropic某台服务器日志里的条目,假以时日,可能某天他们会决定第三方应用制造的"噪声"已经太多了,直接把我的服务切断。 所以问题的关键不是“怎样更快地轮询”,而是**“怎样尽量少地轮询,同时还不遗漏关键信息?”** 这时候,一个理性的工程师可能写一个简单的if,但一个对自嘲式过度设计充满愉悦感的工程师,可能最终选择实现一个卡尔曼滤波器。 最简单的解决方案(以及它为何失败) 最直接的想法很简单:如果这个数字没变,就别问了。 如果 当前值 == 上一个值: 增加等待时间 否则: 回到30秒轮询 这个方法表现得很差。因为这个数字只有在**你有操作**的时候(比如你发送了消息,或使用了 tokens)才会改变。但在你**没有操作**时,它也会变化——比如系统设置了5个小时的滑动窗口期限,导致过期的tokens自动失效。如果你在其他设备上也在用这项服务,数字也会在你不知情的情况下变动。 所以单靠检查数字是否变化是不够的。你需要的是**预测**它什么时候会发生变化,并以多大的信心预测。 ## 卡尔曼滤波器:五分钟速成 卡尔曼滤波器是一种用来融合两种不完美信息的工具。 想象你身处一个没有窗户的房间中特别想知道室外的气温。你有两个渠道: 1. **你的心理模型**:例如 “现在是三月的下午三点,气温应该在18℃左右”。这个估计是合理的,但并不完美——可能外面刚下过雨,或者有风。 2. **一个嘈杂的廉价温度计**:你可以走到阳台测量,但这个温度计有±3℃的波动误差。 两种渠道都不完美,但卡尔曼滤波器告诉你:**把这两种信息结合起来,并在不同时刻对更可靠的信息赋予更大的权重。** 如果你刚刚10秒前查看了温度计,你的心理模型就很可靠——完全可以信任它,不用再去测量。如果你已经一个小时没测量了,你的模型就会失去可信度——这时候你得回阳台看看。 关键是**方差**:一个用来衡量“我对当前估计值有多信任”的数值。在刚看过温度计后的瞬间,方差为零;随着时间推移,方差逐渐增大。当超过某个门槛时,滤波器会告诉你:“不可信了,去获取真实数据吧。” 在我的案例里: - **心理模型** = tokens的本地使用情况。我知道Claude Code的使用记录,所以大致可以推算出消耗的token数是多少。 - **测量数据** = Anthropic的API真实返回值。它是当前最准确的信息,但获取每一次数据都有政治代价和电力消耗。 - **方差** = 随时间增长的不确定性。如果我在浏览器或手机上使用了服务,本地模型对此一无所知——也因此预测的准确性会下降。 一个复杂的多维卡尔曼滤波器(带协方差矩阵)用来解决这个问题有点类似杀鸡用牛刀。我的方法更简单:一个单一状态(token使用量)、单一传感器(API返回值)、线性模型(成本/预算比例)。20行代码就搞定了,这是可以完美解决问题的最小实现。 翻译到我的具体需求中: - **预测**: `使用量估计 = 上次真实值 + (本地新增消耗 / 总预算) × 100` - **修正**: 每次服务器返回一个新的真实值,滤波器会将方差重置为零。 - **不确定性**: 方差开始按照时间线性增长。`σ = √(Q × 自上次校正起的秒数)`。 ## 关键点:滤波器自动决定何时请求 这里正是过度设计显得合理的地方。卡尔曼滤波器不仅仅能估计值,它还能**决定何时需要获取真实数据**。五条规则,每个“tick”执行一次: | 规则 | 触发条件 | 原因 | |-------|-----------|---------| | 窗口已重置 | `now ≥ resetsAt` | tokens过期了,之前的值失效。 | | 高不确定性 | `σ > 5%` | 对预测值信任度低,必须确认。 | | 范围边界触发 | 置信区间触碰到80%、95%或100% | 临近重要阈值,用户必须知道。 | | 邻近性触发 | `使用率`接近边界8% | 有可能跨越范围,但本地估计未能察觉(比如在其他设备使用服务时)。 | | 安全超时 | 15分钟无真实数据返回 | 预防性措施,监控软件的偏执有时是优点。 | 如果没有规则触发,卡尔曼滤波器会说:“没问题,我全都掌握”——于是应用**不会发起HTTP请求**。显示给用户的值是本地估计值。 重点来了:本地估计不需要**网络消耗、功耗或风险**。它完全基于内存中的数学运算。 ## 数字对比:前后变化 小范围内token消耗稳定的一天(中度使用,较少峰值): | 场景 | 请求/小时 | 请求/天(8小时) | |---------|:-:|:-:| | 固定30秒轮询 | 120 | 960 | | 使用贝叶斯估计 | 15-30 | 120-240 | | 估计 + 休眠 | 4-10 | 30-80 | 相比之前的960次请求,网络请求减少了**75-97%**。对于“只是偶尔请求数据”的任务来说,不算太糟吧? ...(内容会继续翻译完整)

2026年3月12日 · Fernando

如何在 Anthropic 断供时估算你的 Claude 配额

我正在构建 Tokamak,一个监控 Claude Max 配额的 macOS 菜单栏应用。几周前,Anthropic 在他们的服务条款中发布了这样的条文: “您不得使用 OAuth 或类似的授权机制来允许第三方应用程序代表用户访问 Claude。” 而我,正在使用浏览器 cookies 调用一个未公开的端点来读取 Claude Max 配额,盯着屏幕想:“现在怎么办?” 今天的工作方式(有用的权宜之计) Tokamak 需要知道你的配额百分比。就是当你在 claude.ai 上用了一段时间后看到的那个 42%。问题是Anthropic 没有公开的配额 API。没有一个带 API key 的文档化的 GET /api/quota。 但确实存在一个 claude.ai 网站自己使用的内部端点: GET /api/organizations/{org_id}/usage 返回类似这样的内容: { "five_hour": { "utilization": 42, "resets_at": "2026-02-22T18:00:00Z" }, "seven_day": { "utilization": 19, "resets_at": "2026-02-28T14:59:59Z" } } 要调用这个端点,你需要已登录用户的会话 cookies。Tokamak 用一个隐藏的 WKWebView 解决这个问题:用户在应用内的 claude.ai 中登录,cookies 保留在 WebView 中,应用每 30 秒使用这些 cookies 进行轮询。 这样做有效。已经运行了好几个月。但这是一个优雅的权宜之计,而不是稳健的解决方案。我们在使用一个内部 API,Anthropic 可以随时更改、破坏或阻止它,而无需预先通知。 法律灰色地带 OAuth 的禁令是明确的。但这适用于 cookies 吗?技术上我们没有使用 OAuth 或"类似的授权机制"。用户直接在标准 WebView 中登录 claude.ai。就像在应用内打开 Safari。 ...

2026年2月22日 · Fernando

macOS公证:苹果为你的应用设置的夜店门卫

凌晨两点。你的应用编译通过。签名完成。打包成DMG。执行notarytool submit。苹果说"In Progress"。你等了5分钟。10分钟。20分钟。一个小时。两个小时。提交仍然是"In Progress"。你睡觉去了。第二天早上:Invalid。 除了"The signature of the binary is invalid"之外没有更多解释。对两种架构都是如此。谢谢苹果。非常有用。 公证是那种完美运行的流程…直到它不工作为止。当它失败时,会给你留下一个Gatekeeper不会打开的.dmg文件和一个什么都不告诉你的错误。在为Tokamak(我的用于监控Claude配额的菜单栏应用)与此斗争几天后,我决定记录所学到的一切并编写一个检查器以避免再次经历这种痛苦。 什么是公证(简单来说) 想象Mac App Store是一个有保安的购物中心。但你不想在购物中心销售——你想直接分发你的应用,用你自己的DMG。就像街边摊位。 苹果说:“好的,你可以。但首先要通过门卫。” 那个门卫就是公证。这是苹果的一个自动化服务,扫描你的已签名应用,验证它不包含已知的恶意软件,如果一切正常,就给你一个票据。你把这个票据钉在你的DMG上(stapler staple),从那时起,当用户下载并尝试打开它时,Gatekeeper看到票据就说"请进"。 没有那个票据,用户会看到这个: “Tokamak.app"无法打开,因为苹果无法检查它是否不含恶意软件。 你的应用就被丢在路边了。 苹果为什么这样做 有两个原因。一个是合理的。另一个…嗯。 合理的原因:保护用户。在公证之前(2019年在macOS 10.14.5中引入),任何人都可以用Developer ID分发已签名的.app,macOS会毫无怨言地打开它。代码签名验证开发者的身份,但不扫描内容。如果你的已签名应用包含键盘记录器,签名完整的情况下也会执行。 公证增加了一层:苹果在到达用户之前扫描二进制文件寻找已知的恶意软件和可疑行为。这不是像App Store那样的人工审查——这是一个自动化系统。但至少是个保障。 另一个原因:控制。苹果希望你通过App Store Connect处理所有事情。使用Developer ID的直接分发一直是二等公民。公证是在"你可以在App Store之外分发,但我们会让你感到不便"这条道路上的又一步。 话虽如此,从macOS 10.15开始,对于所有在App Store之外分发的应用,公证是强制性的。这不是可选的。你的应用要么经过公证,要么不会打开。没有商量余地。 会让你浪费数小时的7个错误 在收集了自己的错误和开发者论坛的错误后,这些是最痛苦的: 1. 缺少--timestamp 这是经典错误。你的codesign在本地完美工作,Gatekeeper不抱怨,但公证返回"The signature of the binary is invalid.” # 错误 — 本地签名有效,苹果拒绝 codesign --force --options runtime --sign "Developer ID Application: ..." MiApp.app # 正确 — 使用苹果服务器的时间戳 codesign --force --options runtime --timestamp --sign "Developer ID Application: ..." MiApp.app 安全时间戳证明签名是在证书有效期内完成的。没有它,苹果不会信任。就像签署没有日期的合同——技术上有效,但没人会接受。 ...

2026年2月22日 · Fernando

一行命令创建 macOS 虚拟机

我正在构建一个 macOS 的菜单栏应用程序。在我的 Mac 上运行完美。现在我需要知道它是否能在干净的 macOS 环境中正常工作:没有我的配置、没有我的权限、没有我的数据。一个全新安装的用户环境。 如何测试这种情况?你需要一个虚拟机。 “简单”,我想。“我安装了 UTM。打开向导,创建一个 macOS 虚拟机,然后运行。” 事情并没有那么简单。 UTM:漂亮但难以驯服 UTM 是一个很棒的应用程序。精心设计的界面,支持在 Apple Silicon 上运行 macOS 客户机,全屏显示,共享剪贴板。手动使用确实很棒。 当你试图自动化时问题就出现了。 UTM 有一个叫做 utmctl 的命令行界面。可以列出虚拟机、启动它们、停止它们、克隆它们。它不能做的是创建虚拟机。对于 macOS 客户机,甚至 UTM 的 AppleScript 也不允许创建它们——操作系统字段被硬编码为 Linux。 简单来说:如果你想在 UTM 中创建 macOS 虚拟机,你必须通过向导手动创建。每次都是。需要点击、下载 IPSW(Apple Silicon 的 macOS 安装映像——相当于传统的 ISO,但由 Apple 打包)、等待安装。 对于需要在质量保证流程中频繁创建和销毁虚拟机的开发者来说,这真是个麻烦事。 Tart:为开发者设计的 macOS 虚拟机 Tart 是当有人在设计虚拟化工具时考虑开发者而不是最终用户的结果。 它使用与 UTM 完全相同的 Apple Virtualization.framework。相同的技术,相同的功能,相同的原生速度。区别在于界面:Tart 是命令行优先的。 brew install cirruslabs/cli/tart 就这样。没有图形界面配置,没有向导。只是在你的 PATH 中的一个二进制文件。 一个命令统治一切 创建一个使用最新可用版本的 macOS 虚拟机: tart create mi-vm --from-ipsw latest 就是这样。latest 告诉 Tart “给我这台 Mac 支持的最新 macOS 版本”。Tart 查询 Apple 的 API,下载 IPSW(约 15 GB——是的,macOS 很大),创建虚拟磁盘,安装操作系统,并为你准备好可以启动的虚拟机。去喝杯咖啡吧,因为需要一段时间——但你不需要动手做任何事情。 ...

2026年2月21日 · Fernando

Apple 给我安装了84GB的水母视频。还是重复的。

Kimi K2 只能等等了 昨天想下载Moonshot的最新模型Kimi K2 Instruct。这个模型看起来很有前景,我已经想测试好几天了。 准备清理空间,看了下磁盘,发现了这个: 磁盘: 927GB 已使用: 644GB 空余: 283GB 嗯。283GB空余空间还不错,但是到底什么东西占用了644GB?我的Mac一直保持得很干净,不在本地存储电影,几乎所有东西都放在云端。 开始调查。然后我想起来了。 45GB的水母视频 我已经知道Apple在其无穷的智慧中,决定在macOS Sonoma中包含45GB的动态壁纸。4K 240fps的水母漂浮、海浪翻滚、北极光和各种风景视频。 我没有要求。我不想要。但它们就在那里。 我不知道的是接下来发生的事情。 反转:它们是重复的 结果macOS在两个不同的位置存储这些视频: # 副本1:系统 /Library/Application Support/com.apple.idleassetsd/Customer/4KSDR240FPS/ → 42GB # 副本2:用户 ~/Library/Application Support/com.apple.wallpaper/aerials/videos/ → 42GB 八十四GB。相同的视频。重复的。 因为显然Apple认为拥有一个系统副本(root拥有)和一个用户副本(fernando拥有)是个好主意。相同的89个.mov文件。相同的UUID。相同的内容。 它们不是硬链接,是真实副本 我想:“好吧,也许是硬链接或APFS克隆。相同文件,相同磁盘空间,一切都好”。 结果不是。 # 不同的inode = 真实副本 ls -i "/.../4KSDR240FPS/009BA758...mov" → 15543749 ls -i "~/.../aerials/videos/009BA758...mov" → 108602438 副本。真实的。八十四GB的水母占用我SSD的真实空间。 256GB SSD多花200欧元 最让我恼火的是背景。Apple卖的Mac配置的SSD对于它们的价格来说小得可笑: 型号 基础SSD 升级到512GB MacBook Air M3 256GB +230欧元 MacBook Pro 14" M3 512GB +230欧元 Mac Mini M4 256GB +230欧元 然后它们给你安装84GB的重复壁纸。 ...

2026年1月29日 · Fernando

10 GB 的虚拟机支持聊天机器人:Claude 在你的 Mac 上到底在做什么

10 GB 的惊喜 你在 Mac 上安装了 Claude Desktop。最初一切正常,这个应用看似占用空间很小。但某天,当你检查磁盘时,发现了这个文件: ~/Library/Application Support/Claude/vm_bundles/claudevm.bundle 10.8 GB。 是的:一个名为 claudevm.bundle 的文件,隐藏在 Claude 的 vm_bundles 文件夹中,竟占用了将近 11 GB 的存储空间。一个聊天机器人居然需要 10 GB?它里面装了什么,魔戒三部曲加长版视频? 其实不是。claudevm.bundle 包含了一个 Ubuntu 系统。 Claude 的三大产品类别 在解释“是什么”之前,先来聊聊“为什么”。Anthropic 提供了三种方式让你使用 Claude: 产品 执行环境 目标用户 claude.ai Anthropic 云服务器 所有人 Claude Desktop + Cowork 你的 Mac 上的虚拟机 专业人士 Claude Code 在你的系统上直接运行 开发者 网页版是最安全的选项:所有操作都在 Anthropic 的云端完成,你的电脑完全不受影响。但如果你希望 Claude 不仅仅用于聊天,还能在你的电脑上“真正做事情”——比如创建文档、执行代码、处理文件等,那你需要更多功能。 这时,Cowork 和 Claude Code 应运而生,两者代表了完全不同的理念。 Cowork:人人都能用的 Claude Code Cowork 是 Anthropic 在 2026 年 1 月 12 日推出的智能助手产品。它的官方口号是: ...

2026年1月25日 · Fernando