
如果你是一名 macOS 开发者或者经常在 Mac 上写技术文档那么你一定经历过这样的场景在 VS Code 或 Typora 里写 Markdown左边是源码右边是预览每次想看效果都得手动切换视图或者忍受预览刷新的延迟。这种割裂感让“所见即所得”的流畅体验大打折扣。今天要聊的这个项目瞄准的正是这个痛点。它不是一个跨平台的 Markdown 编辑器而是一个旗帜鲜明的“macOS-first”原生应用用 100% Swift 和 AppKit/TextKit 打造核心卖点是“行内实时预览”。这意味着你无需切换任何模式在编辑源码的同时预览效果就直接、实时地呈现在同一行文字旁边。这听起来像是一个微小的改进但背后却是一个清晰的判断对于追求极致写作效率和原生体验的 macOS 用户来说一个深度集成系统能力、响应迅捷、界面优雅的专用编辑器其价值远大于功能庞杂的通用工具。它解决的不仅是“预览”问题更是“心流”问题——让你专注于内容创作而非工具本身。本文将带你深入剖析这个项目。我们不止会看它“是什么”更会探讨为什么“macOS-first”和“100% Swift”在今天依然重要行内实时预览在技术实现上与传统双栏预览有何本质不同作为一个开发者如何从零开始构建或理解这样一个应用在实际使用中它可能面临哪些挑战和局限无论你是想寻找下一款顺手的写作工具还是对 Swift 和 AppKit 开发感兴趣希望学习如何构建一个现代化的原生 macOS 应用这篇文章都将提供从概念到实操的完整路径。1. 行内实时预览不仅仅是另一个预览模式在深入代码之前我们必须先理解“行内实时预览”与传统预览方式的根本区别。这决定了整个应用的设计哲学和技术架构。传统方案双栏/分屏预览绝大多数 Markdown 编辑器如 VS Code配合插件、Typora需切换模式、甚至一些在线编辑器都采用“源码-预览”分离的模型。你在一侧书写纯文本标记在另一侧查看渲染后的 HTML 效果。这种模式的弊端显而易见上下文切换成本高视线和注意力需要在两个窗口间来回跳动尤其是编写长文档时容易迷失位置。实时性依赖刷新预览窗口的更新可能有延迟即使是自动刷新无法做到真正的“零延迟”反馈。空间占用需要并排显示两个视图对小屏幕笔记本用户不友好。行内实时预览方案该项目的方案是将预览“嵌入”到编辑流中。想象一下当你输入**加粗**时这两个星号中间的文本在输入完成的瞬间就直接在编辑器中显示为加粗的样式但背景可能略有不同或者星号变为半透明以提示你这是渲染后的效果同时保留了可编辑性。这种模式的核心优势在于零距离反馈所见即所得且“所得”就在“所见”之处极大减少了认知负荷。保持线性阅读流文档始终以最终呈现的视觉流进行排布符合阅读习惯。潜在的高性能由于只渲染当前视口或发生变化的行理论上可以比全文档渲染更快。然而实现这种模式的挑战巨大编辑与渲染的融合需要在一个NSTextView(或等价组件) 中同时处理纯文本编辑和富文本显示这涉及到复杂的TextKit布局管理。语法高亮与预览的区分需要清晰地区分哪些是 Markdown 语法标记应被隐藏或淡化哪些是应渲染的内容如加粗、标题。光标定位与编辑体验当文本被渲染后光标如何定位点击渲染后的加粗文本是选中整个**文本**还是仅选中文本删除操作应该如何进行这需要精细的NSLayoutManager和NSTextStorage定制。理解了这些我们就能明白为什么作者强调“100% Swift”和“AppKit/TextKit”。只有深度使用这些原生框架才能实现对文本渲染管线最精细的控制从而打造出流畅、原生的编辑体验。这正是“macOS-first”的底气所在。2. 技术栈深度解析为何是 Swift AppKit TextKit选择 Swift 和 AppKit 而非 Electron 或跨平台框架是一个充满“苹果生态”特色的技术决策。我们来拆解其背后的考量。2.1 Swift性能、安全与现代语法性能Swift 编译为高效的本地机器码在文本实时解析和渲染这类 CPU 密集型任务上相比 JavaScript (Electron) 或解释型语言有天然优势。低延迟是行内预览的生命线。内存安全Swift 的 ARC (自动引用计数) 和值语义设计减少了内存泄漏和野指针的风险对于需要长时间运行、处理复杂文本数据的编辑器应用至关重要。与 Cocoa 框架无缝集成Swift 与 Objective-C 的互操作性极佳可以毫无障碍地使用成熟的 AppKit 和 TextKit 框架。2.2 AppKit打造真正的原生 macOS 应用系统集成AppKit 提供了真正的 macOS 原生 UI 控件、菜单栏、服务、拖放、快捷键绑定、深色模式自动适配等。应用可以拥有 macOS 标准的窗口行为、字体渲染和辅助功能支持。性能与能效原生控件由系统直接绘制效率远高于 Web 技术模拟的控件。这对于追求续航的 MacBook 用户是一个重要优点。小而美的二进制文件一个纯粹的 Swift/AppKit 应用其最终发布的.app文件体积通常只有几十 MB而一个功能类似的 Electron 应用动辄超过 100 MB。2.3 TextKit文本渲染的基石这是实现行内预览的核心框架。TextKit 是 Apple 用于文本布局和渲染的高级框架NSTextView就是构建于其上。NSTextStorage 存储文本内容和属性。我们可以在这里“挂钩”监听文本变化并实时将 Markdown 语法转换为富文本属性如字体、颜色。NSLayoutManager 管理文本的布局和显示。我们可以定制它来控制如何绘制文本背景、如何将文本片段映射到不同的渲染样式例如将**标记绘制为透明而将其中的文本绘制为加粗。NSTextContainer 定义文本可以布局的区域。 通过定制这三个核心对象开发者可以实现极其灵活的文本显示效果这正是实现“行内预览”的技术基础。对比跨平台方案特性Swift/AppKit (本项目)Electron/Web技术启动速度快原生二进制慢需启动 Chromium 实例内存占用低高每个应用包含完整浏览器内核系统集成深度集成原生体验较浅依赖桥接UI 响应极快直接调用系统 API良好但经过多层抽象开发效率高 (对于熟悉 Cocoa 者)极高前端生态丰富跨平台仅 macOSWindows, macOS, Linux应用体积小(几十 MB)大 (上百 MB)对于一款定位为“macOS-first”、追求极致体验的工具Swift/AppKit 几乎是必然之选。3. 环境准备搭建 Swift 开发环境要运行、研究甚至贡献这个项目你需要一个完整的 Swift 开发环境。以下是详细步骤。3.1 系统与工具要求操作系统macOS 12 (Monterey) 或更高版本。建议使用最新稳定版以获得最好的开发工具支持。Xcode这是核心开发工具。从 Mac App Store 免费下载并安装最新稳定版本的 Xcode。安装后务必打开一次 Xcode 以完成命令行工具的安装。Homebrew(可选但推荐)macOS 的包管理器用于安装其他辅助工具。/bin/bash -c $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)3.2 验证安装打开终端 (Terminal)运行以下命令检查基础环境# 检查 Xcode 命令行工具是否安装 xcode-select -p # 预期输出类似/Applications/Xcode.app/Contents/Developer # 检查 Swift 编译器版本 swift --version # 预期输出包含 Swift 版本号例如swift-driver version: 1.87.1 Apple Swift version 5.10 # 检查是否可创建 Swift 包 swift package init --type executable # 成功后会在当前目录生成一个基础的 Swift 包可以删除它rm -rf MySwiftPackage3.3 获取项目源码假设该项目托管在 GitHub 上这是 Show HN 项目的常见平台。你需要使用git来克隆代码。# 进入你的开发目录 cd ~/Developer # 克隆项目 (请将 URL 替换为实际的项目地址) git clone https://github.com/username/macos-markdown-editor.git # 进入项目目录 cd macos-markdown-editor如果项目使用 Swift Package Manager (SPM)打开目录下的Package.swift文件即可了解项目结构。3.4 使用 Xcode 打开项目通常Swift 项目可以通过xed .命令在当前目录打开 Xcode。xed .或者如果项目包含.xcodeproj或.xcworkspace文件直接双击它即可。在 Xcode 中你需要选择正确的签名团队Signing Capabilities才能编译运行。对于个人开发可以选择“Personal Team”进行免费签名。4. 项目结构与核心流程拆解让我们以一个典型的 Swift/AppKit Markdown 编辑器项目结构为例解析其核心模块是如何协同工作的。macOS-Markdown-Editor/ ├── Package.swift # Swift 包清单文件 ├── Sources/ │ └── MarkdownEditor/ # 主目标模块 │ ├── App/ # 应用层 │ │ ├── AppDelegate.swift # 应用生命周期委托 │ │ └── MainMenu.swift # 主菜单定义 │ ├── Views/ # 视图层 │ │ ├── EditorViewController.swift # 编辑器主视图控制器 │ │ └── PreviewTextView.swift # 核心的定制文本视图 │ ├── Models/ # 模型层 │ │ ├── Document.swift # 文档数据模型 │ │ └── MarkdownParser.swift # Markdown 解析器 │ ├── Utils/ # 工具类 │ │ └── Extensions.swift # 扩展方法 │ └── Resources/ # 资源文件 │ ├── Assets.xcassets # 图标等资源 │ └── Info.plist # 应用配置文件 └── README.md核心工作流程如下启动与初始化(AppDelegate.swift)应用启动创建主窗口加载EditorViewController。文本输入(PreviewTextView.swift)用户在定制的NSTextView子类中输入文本。文本变更监听(PreviewTextView或EditorViewController)通过textDidChange通知或NSTextStorageDelegate捕获文本变化。Markdown 解析(MarkdownParser.swift)将当前段落或受影响范围的纯文本传递给解析器。解析器需要快速识别出 Markdown 标记如#,**,,[]()等。属性计算(MarkdownParser): 解析器计算出对应的富文本属性NSAttributedString.Key例如# 标题- 字体变大、加粗。**加粗**- 添加.bold字体特征并可能将**标记的foregroundColor设为透明。代码- 等宽字体、灰色背景。应用属性到文本存储(PreviewTextView): 将计算好的属性应用到NSTextStorage的相应范围。关键点这里不是替换文本而是修改文本的显示属性。自定义绘制(PreviewTextView的NSLayoutManagerDelegate): 为了实现更复杂的行内预览如代码块背景、水平线可能需要实现drawBackground等方法在NSLayoutManager的协调下进行自定义绘制。光标与编辑处理(PreviewTextView): 重写鼠标点击和键盘事件处理方法确保在渲染后的文本上进行编辑时光标能正确定位到背后的纯文本源码位置。5. 核心代码实现构建一个简易行内预览编辑器我们来实现一个最核心的简化版本一个能实时将**加粗**和代码渲染出来的NSTextView。5.1 创建自定义文本视图 (PreviewTextView.swift)// 文件PreviewTextView.swift import AppKit class PreviewTextView: NSTextView { private let parser MarkdownParser() override init(frame frameRect: NSRect, textContainer container: NSTextContainer?) { super.init(frame: frameRect, textContainer: container) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } private func commonInit() { // 1. 启用连续拼写检查可选 self.isContinuousSpellCheckingEnabled true self.autoresizingMask [.width, .height] // 2. 设置等宽字体适合代码编辑 self.font NSFont.monospacedSystemFont(ofSize: 14, weight: .regular) // 3. 设置文本存储的委托以监听文本变化 self.textStorage?.delegate self } // 处理文本变化后的渲染 private func processTextChange() { guard let textStorage self.textStorage else { return } let fullRange NSRange(location: 0, length: textStorage.length) // 先清除所有自定义属性恢复原始状态 textStorage.removeAttribute(.font, range: fullRange) textStorage.removeAttribute(.foregroundColor, range: fullRange) textStorage.removeAttribute(.backgroundColor, range: fullRange) textStorage.addAttribute(.foregroundColor, value: NSColor.textColor, range: fullRange) textStorage.font NSFont.monospacedSystemFont(ofSize: 14, weight: .regular) // 将纯文本传递给解析器进行处理 let plainText textStorage.string parser.applyMarkdownAttributes(to: textStorage, in: fullRange, for: plainText) } } // 扩展实现 NSTextStorageDelegate extension PreviewTextView: NSTextStorageDelegate { func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) { // 当文本编辑完成时非连续编辑进行处理 if editedMask.contains(.editedCharacters) { // 为了性能可以延迟一小段时间或使用去抖(debounce) DispatchQueue.main.async { [weak self] in self?.processTextChange() } } } }5.2 实现一个简单的 Markdown 解析器 (MarkdownParser.swift)// 文件MarkdownParser.swift import AppKit class MarkdownParser { // 定义一些样式 private let boldFont NSFont.monospacedSystemFont(ofSize: 14, weight: .bold) private let codeFont NSFont.monospacedSystemFont(ofSize: 13, weight: .regular) private let codeBackgroundColor NSColor.tertiaryLabelColor.withAlphaComponent(0.1) func applyMarkdownAttributes(to textStorage: NSTextStorage, in range: NSRange, for plainText: String) { let nsString plainText as NSString // 1. 处理加粗语法**text** 或 __text__ let boldPattern try! NSRegularExpression(pattern: \\*\\*(.?)\\*\\*|__(.?)__, options: []) boldPattern.enumerateMatches(in: plainText, options: [], range: range) { match, _, _ in guard let matchRange match?.range else { return } // 确定捕获组** 或 __ 内部文本的范围 let innerRange match?.range(at: 1) ?? match?.range(at: 2) if let innerRange innerRange, innerRange.location ! NSNotFound { // 将内部文本设置为粗体 textStorage.addAttribute(.font, value: boldFont, range: innerRange) // 可选将标记符号 (** 或 __) 的颜色设为透明或浅灰色 let markerLength (matchRange.length - innerRange.length) / 2 let startMarkerRange NSRange(location: matchRange.location, length: markerLength) let endMarkerRange NSRange(location: matchRange.location markerLength innerRange.length, length: markerLength) textStorage.addAttribute(.foregroundColor, value: NSColor.tertiaryLabelColor, range: startMarkerRange) textStorage.addAttribute(.foregroundColor, value: NSColor.tertiaryLabelColor, range: endMarkerRange) } } // 2. 处理行内代码语法code let inlineCodePattern try! NSRegularExpression(pattern: ([^\n]?), options: []) inlineCodePattern.enumerateMatches(in: plainText, options: [], range: range) { match, _, _ in guard let matchRange match?.range, let innerRange match?.range(at: 1) else { return } // 将内部文本设置为等宽字体和背景色 textStorage.addAttribute(.font, value: codeFont, range: innerRange) textStorage.addAttribute(.backgroundColor, value: codeBackgroundColor, range: matchRange) // 将反引号标记的颜色设为透明或浅灰色 let startMarkerRange NSRange(location: matchRange.location, length: 1) let endMarkerRange NSRange(location: matchRange.location matchRange.length - 1, length: 1) textStorage.addAttribute(.foregroundColor, value: NSColor.tertiaryLabelColor, range: startMarkerRange) textStorage.addAttribute(.foregroundColor, value: NSColor.tertiaryLabelColor, range: endMarkerRange) } // 注意这是一个极其简化的示例。真实的解析器需要处理嵌套、冲突、多行代码块、链接、图片等复杂情况。 // 并且需要考虑性能避免在每次击键时都全文解析。 } }5.3 主视图控制器集成 (EditorViewController.swift)// 文件EditorViewController.swift import AppKit class EditorViewController: NSViewController { var previewTextView: PreviewTextView! override func loadView() { // 创建容器视图 self.view NSView(frame: NSRect(x: 0, y: 0, width: 800, height: 600)) self.view.wantsLayer true self.view.layer?.backgroundColor NSColor.windowBackgroundColor.cgColor // 创建并配置自定义文本视图 let scrollView NSScrollView(frame: self.view.bounds) scrollView.autoresizingMask [.width, .height] scrollView.hasVerticalScroller true scrollView.hasHorizontalScroller false // 通常 Markdown 编辑器禁用水平滚动 scrollView.autohidesScrollers true let textContainer NSTextContainer(containerSize: NSSize(width: scrollView.contentSize.width, height: .greatestFiniteMagnitude)) textContainer.widthTracksTextView true textContainer.heightTracksTextView false let layoutManager NSLayoutManager() layoutManager.addTextContainer(textContainer) let textStorage NSTextStorage() textStorage.addLayoutManager(layoutManager) previewTextView PreviewTextView(frame: .zero, textContainer: textContainer) previewTextView.minSize NSSize(width: 0, height: 0) previewTextView.maxSize NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) previewTextView.isVerticallyResizable true previewTextView.isHorizontallyResizable false previewTextView.autoresizingMask [.width] scrollView.documentView previewTextView self.view.addSubview(scrollView) // 设置初始示例文本 let sampleText # 欢迎使用 Markdown 编辑器 这是一个 **加粗文本** 的示例。 这是一个 行内代码 的示例。 你可以继续输入 **更多的标记** 来测试实时预览。 previewTextView.string sampleText } }6. 编译、运行与效果验证6.1 编译与运行在 Xcode 中确保EditorViewController被设置为应用的初始视图控制器通常在AppDelegate中设置或通过 Storyboard 指定。选择你的开发团队进行签名。点击 Xcode 左上角的运行按钮 (▶️) 或按Cmd R。6.2 预期效果应用启动后你将看到一个简单的窗口里面有一个文本编辑区域。初始文本中# 欢迎使用 Markdown 编辑器可能还没有被渲染为标题因为我们示例代码未实现标题解析。**加粗文本**中的“加粗文本”四个字应该显示为粗体而**符号可能变为浅灰色。行内代码中的“行内代码”四个字应该有浅灰色背景并使用等宽字体反引号变为浅灰色。6.3 交互测试实时输入在新的一行输入**测试**当你输入完第二个*时“测试”二字应立即变为粗体。编辑已渲染文本用鼠标点击已经渲染为粗体的“加粗文本”光标应能正确定位。尝试在中间插入或删除文字观察渲染是否实时更新。性能观察输入一段很长的文本例如粘贴一篇长文章观察滚动和编辑是否流畅。由于我们的简易解析器在每次编辑后都处理全文在长文档下可能会有卡顿这正说明了优化的重要性。6.4 验证成功的关键点功能正确Markdown 语法被正确识别并转换为视觉样式。实时性样式变化几乎与输入同步无明显延迟。可编辑性渲染后的文本区域仍然可以正常接收光标和键盘输入。原生感应用响应迅速符合 macOS 应用的交互预期如复制粘贴、字体平滑等。7. 常见问题与排查思路在开发和使用此类编辑器时你会遇到一些典型问题。以下是一个排查指南问题现象可能原因排查方式解决方案输入时应用卡顿、滚动不流畅1. 解析器性能差每次都在全文范围进行正则匹配。2.textStorageDidProcessEditing中执行了耗时操作阻塞主线程。3. 属性计算或应用过于频繁。1. 使用 Instruments 的 Time Profiler 工具分析耗时函数。2. 在textStorageDidProcessEditing中打印editedRange确认是否在合理范围。3. 检查是否在短时间内触发了多次文本处理。1.优化解析仅解析受影响的段落或行 (editedRange)。使用更高效的状态机或解析器如cmark。2.异步/延迟处理使用DispatchQueue.main.async或去抖 (debounce) 机制避免每次击键都立即处理。3.缓存结果对未变化的文本段落缓存其属性。Markdown 语法标记如**仍然可见未隐藏或淡化1. 解析器未正确计算标记的范围。2. 属性应用错误foregroundColor未设置为透明或浅色。3. 自定义的NSLayoutManager绘制逻辑覆盖了文本颜色。1. 调试MarkdownParser.applyMarkdownAttributes打印matchRange和innerRange。2. 检查应用到标记范围的foregroundColor属性值。3. 检查drawGlyphs或drawBackground方法是否错误绘制。1. 确保正则表达式能准确捕获标记和内容。2. 将标记的foregroundColor设为NSColor.clear或NSColor.tertiaryLabelColor。3. 在自定义绘制中确保只绘制你需要的背景等不要干扰文本属性。点击渲染后的文本光标定位到错误的位置1. 文本存储 (NSTextStorage) 中的字符索引与显示位置映射错误。2. 自定义的mouseDown或光标定位逻辑有误。1. 重写clickedLocation相关方法打印点击位置和转换后的字符索引。2. 对比点击纯文本区域和渲染区域的差异。1. 通常不需要重写点击事件。问题根源在于字符索引未变。确保你的处理只修改属性 (NSAttributedString.Key)而不插入或删除字符。光标定位基于字符索引只要索引正确定位就正确。复制粘贴出来的文本带有 Markdown 语法标记复制操作默认复制NSTextStorage中的字符串其中包含原始标记。重写文本视图的writeSelection(to:type:)或pasteboard相关方法。提供两种复制格式富文本 (RTF) 和纯文本。在纯文本格式中可以提供一个“复制为纯文本不含标记”的选项这需要解析当前选中内容并去除标记。无法正确处理多行代码块或复杂嵌套简易的正则表达式无法处理跨行和嵌套结构。测试输入包含嵌套**boldcodeinside**或多行代码块的文本。需要实现更强大的解析器如基于语法树 (AST) 的解析。可以考虑集成开源的 C 库cmark(CommonMark) 或其 Swift 封装 (如Ink)它们能准确解析复杂 Markdown。应用启动崩溃1. 签名问题。2. Storyboard/XIB 连接错误。3. 强制解包 (!) 了一个 nil 值。1. 查看 Xcode 控制台崩溃日志。2. 检查AppDelegate和主 Storyboard 的配置。3. 使用po命令在崩溃处检查变量。1. 确保在 Xcode 的 Signing Capabilities 中选择了有效的团队。2. 检查PreviewTextView在 Storyboard 中的自定义类设置是否正确。3. 将可疑的!改为安全的if let或guard let。8. 最佳实践与工程建议如果你想基于此概念构建一个可用于生产环境的编辑器以下建议至关重要8.1 性能优化增量解析永远不要在全文档范围进行解析。利用NSTextStorageDelegate提供的editedRange只解析受影响的段落。Markdown 是段落敏感的这很有效。使用高效解析器正则表达式适合简单语法对于完整的 CommonMark 标准应使用专业的解析器库如cmark(C库) 或Ink(Swift库)。它们生成 AST便于后续处理和渲染。主线程优化文本属性操作必须在主线程但解析计算可以放在后台队列。在后台生成属性字典然后回到主线程应用到NSTextStorage。属性去重避免对同一段文本重复应用相同的属性。8.2 用户体验可配置的渲染提供设置选项允许用户关闭行内预览、调整标记颜色、选择主题等。语法高亮扩展除了 Markdown可以为嵌入的代码块提供语法高亮可使用Highlightr等库。撤销/重做支持确保对文本属性的修改也纳入应用的撤销管理器中。这需要将属性变化包装成NSUndoManager可识别的操作。导出功能提供一键导出为 HTML、PDF、Word 等功能。利用解析后的 AST 可以轻松生成多种格式。8.3 代码结构分离关注点保持MarkdownParser独立只负责解析文本并返回“样式指令”数组如在范围 R 应用粗体。由另一个Renderer类负责将这些指令应用到NSTextStorage。这样便于单元测试和更换解析引擎。使用 Swift Concurrency对于异步解析任务使用async/await可以使代码更清晰。完善的错误处理解析器可能会遇到畸形 Markdown要确保应用不会因此崩溃。8.4 生产环境考量内存管理处理超大文档时注意NSTextStorage的内存占用。可以考虑按需加载的机制。文件自动保存实现NSDocument架构支持版本管理和自动保存。国际化与辅助功能确保应用支持 VoiceOver 等辅助技术。沙盒与权限如果上架 Mac App Store需要适配沙盒机制妥善处理文件访问权限。9. 总结通过拆解这个“macOS-first Markdown editor with inline live preview”项目我们看到了一个看似简单的功能背后所蕴含的深厚技术考量。它不仅仅是一个编辑器更是对 macOS 原生开发生态Swift, AppKit, TextKit的一次深度运用。对于使用者而言如果你厌倦了 Electron 应用的臃肿和跨平台工具的妥协这样一款专注、快速、原生的编辑器无疑是提升写作体验的利器。它证明了在特定平台做深做透依然能产生巨大价值。对于开发者而言这个项目是一个绝佳的 Swift 和 AppKit 学习案例。它涵盖了自定义文本渲染通过TextKit栈实现复杂的文本显示逻辑。响应式UI实时处理用户输入并更新界面。性能权衡在实时性和资源消耗间找到平衡。原生应用架构如何组织一个现代化的 macOS 应用。从简单的正则匹配到完整的 AST 解析从基础的样式应用到复杂的交互处理每一步都充满了挑战和优化的空间。你可以从本文提供的简易示例出发逐步添加对标题、列表、引用、链接、图片乃至图表和数学公式的支持最终打造出属于你自己的、独一无二的写作工具。技术的价值在于解决真实问题。这个项目解决的是“流畅书写”的问题而通过学习和实践它你解决的是“如何用 Swift 构建优秀原生应用”的问题。两者都值得投入时间。建议将本文的示例代码作为起点结合官方文档和更成熟的开源项目如MacDown的源码深入探索你一定会对 macOS 开发有更深的理解。