本文信息:SpacePeek 是一款 2026 年 6 月上架 Mac App Store 的文件夹 Quick Look 扩展工具,由开发者「府王」独立开发。本文基于其公开的 README、App Store 元数据、二进制行为以及同类开源项目(PeekX)的对照,对其技术架构进行逆向推断。本文非官方技术文档,推断可能存在偏差。 如果你正在考虑用类似技术栈开发 macOS 工具,本文的分析框架可作为参考。
一、问题背景:为什么值得拆解这个 App
我注意到它的原因
SpacePeek 是一个让你在 Finder 里选中文件夹按空格就能看到大小和存储分布的工具。作为 macOS 用户,这个功能我已经想了十年——Apple 的 Quick Look 支持 50+ 文件格式,唯独不支持文件夹的存储分析。
但让我真正产生好奇的,不是它的功能,而是它在 README 里的一句技术声明:
“Uses a high-performance Rust scanning engine and polished SwiftUI interface.”
这句话信息密度极高:
-
Rust 扫描引擎 —— 意味着它没有用 Swift 的
FileManager做文件系统遍历 -
SwiftUI 界面 —— 意味着它跑在 Quick Look Extension 这个受限的沙盒环境中
-
两者混编 —— 意味着存在某种 FFI 桥接方案
一个独立开发者,在一款售价 $0(Pro $1.99)的工具里,用了这个技术组合。这不是"杀鸡用牛刀",而是在正确的地方用正确的工具。
阅读收益
读完本文你会理解:
-
Quick Look Extension 的架构约束和开发模型
-
SwiftUI + Rust 混编在 macOS 上的可行方案
-
文件系统遍历的性能瓶颈在哪里,Rust 为什么是正确答案
-
如何用同类开源项目(PeekX)对照验证你的架构推断
⚠️ 声明:SpacePeek 的 GitHub 仓库(
wangfu91/SpacePeek)仅包含营销网站页面,不含应用源码。以下分析基于公开信息、App 行为观察和同类开源项目的对照推断。这不是官方解密,而是一次技术侦探。
二、架构总览
基于 SpacePeek 的产品行为,可以画出以下分层架构图:
┌─────────────────────────────────────────────────────┐
│ Finder │
│ 用户选中文件夹 → 按下空格键 │
└──────────────────────┬──────────────────────────────┘
│ QLPreviewPanel API
▼
┌─────────────────────────────────────────────────────┐
│ Quick Look Preview Extension (.appex) │
│ │
│ ┌───────────────────────────────────────────────┐ │
│ │ SwiftUI 界面层 │ │
│ │ • 总览面板:文件夹名 + 总大小 │ │
│ │ • 存储分布饼图(Charts framework) │ │
│ │ • 文件/文件夹排名列表 │ │
│ │ • Git 元数据展示 │ │
│ │ • 文件年龄分析 │ │
│ │ • IAP 解锁状态管理 │ │
│ └────────────────────┬──────────────────────────┘ │
│ │ Swift ↔ Rust FFI 桥接 │
│ ┌────────────────────▼──────────────────────────┐ │
│ │ Rust 扫描引擎(.dylib / .a) │ │
│ │ • 递归目录遍历 │ │
│ │ • 并行元数据读取 │ │
│ │ • 大小聚合与 Top-N 排序 │ │
│ │ • 结构化结果序列化 │ │
│ └────────────────────┬──────────────────────────┘ │
│ │ │
│ ┌────────────────────▼──────────────────────────┐ │
│ │ macOS 文件系统 API │ │
│ │ • fts(3) / getattrlistbulk(2) │ │
│ │ • Unix stat 系统调用 │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
对照项目:PeekX 的纯 Swift 架构
开源项目 PeekX 提供了同类功能的纯 Swift 实现作为对照:
PeekX/
├── PeekX/ # Main application
│ ├── PeekXApp.swift # App entry point (注册扩展后退出)
└── PeekXExt/ # Quick Look extension
├── PreviewViewController.swift # 预览逻辑
└── Info.plist # 扩展配置
PeekX 使用 纯 Swift + FileManager 做文件系统遍历。这正是 SpacePeek 选择 Rust 的对比背景——我们稍后分析为什么纯 Swift 方案有性能瓶颈。
三、关键技术点逐一拆解
3.1 Quick Look Extension 的架构约束
SpacePeek 选择 Quick Look Extension 而非独立 App,这个决策本身就有深远的技术影响。
什么是 Quick Look Extension:
macOS 允许开发者为特定文件类型(UTI)注册预览扩展。当用户在 Finder 中选中该类型的文件并按空格键时,系统加载扩展的 .appex 二进制,调用 preparePreviewOfFile(at:completionHandler:),展示预览内容。
两种实现方式:
| 方式 | 入口 | 适用场景 |
|---|---|---|
| ViewController-based | QLPreviewingController 协议 | 动态生成预览(SpacePeek / PeekX 的方式) |
| Data-based | 返回固定的预览数据 | PDF、图片等静态内容 |
SpacePeek 注册的 UTI 是 public.folder,这意味着它拦截了系统默认的文件夹预览。配置在 Info.plist 中大概是这样的:
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.quicklook.preview</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PreviewViewController</string>
<key>QLSupportedContentTypes</key>
<array>
<string>public.folder</string>
</array>
</dict>
关键约束:
| 约束 | 影响 |
|---|---|
| 沙盒运行 | 只能访问用户明确选中的文件夹路径;不能读写任意位置 |
| 内存限制 | Extension 进程内存有上限(通常 ~50-100MB),大文件夹扫描可能被 jetsam 杀掉 |
| 生命周期 | 每次预览创建新进程或复用,不能假设状态持久化 |
| 无后台权限 | 不能预先建索引;每次按空格都是"冷启动"扫描 |
这些约束解释了为什么扫描引擎必须快——不是"有一次性的后台索引时间",而是"每次按空格都要在毫秒级完成"。这就是 Rust 选择的核心原因。
3.2 为什么用 Rust 而不是纯 Swift 的 FileManager?
这是整个架构最值得展开的技术决策。让我们从数据上对比两类方案的性能差异。
Swift 遍历方案(对照:PeekX):
// 典型的 Swift FileManager 递归遍历
let enumerator = FileManager.default.enumerator(
at: folderURL,
includingPropertiesForKeys: [.fileSizeKey, .isDirectoryKey],
options: [.skipsHiddenFiles]
)
var totalSize: Int64 = 0
while let fileURL = enumerator?.nextObject() as? URL {
let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey])
totalSize += Int64(resourceValues.fileSize ?? 0)
}
问题在哪:
| 瓶颈 | 原因 |
|---|---|
resourceValues(forKeys:) 每次都做系统调用 | 不是批量获取属性,而是在循环中逐文件 stat() |
NSEnumerator 是单线程的 | 即使是 Apple Silicon 的多核,遍历只用了一个核心 |
| ARC 开销 | 每个 URL 对象的创建/销毁都有引用计数操作 |
| Swift 没有零成本迭代器 | while let + nextObject() 不是编译期展开的 |
Rust 方案(推断):
// 推断——Rust 侧的大致实现
use jwalk::WalkDir; // 或 walkdir
use rayon::prelude::*;
fn scan_folder(path: &Path) -> ScanResult {
let entries: Vec<_> = WalkDir::new(path)
.skip_hidden(false)
.into_iter()
.par_bridge() // rayon 并行桥接
.filter_map(|e| e.ok())
.map(|entry| {
let metadata = entry.metadata().ok()?;
Some(FileInfo {
path: entry.path(),
size: metadata.len(),
is_dir: metadata.is_dir(),
})
})
.collect();
// 聚合统计...
}
Rust 为什么更快:
| 优势 | 实现细节 |
|---|---|
| 并行遍历 | jwalk(或 walkdir)+ rayon::par_bridge(),一个含 50 万文件的文件夹可以利用全部核心 |
| 批量系统调用 | jwalk 内部使用 readdir + 批量 stat,比 Swift 的逐文件 resourceValues 高效一个数量级 |
| 无 ARC 开销 | Rust 的所有权模型,FileInfo 结构体在栈上或堆上直接分配,无引用计数 |
| 编译期优化 | LLVM 对 Rust 的迭代器链做了极致的 loop fusion/inline |
实测对比(推测数量级):
| 文件夹规模 | Swift FileManager | Rust jwalk + rayon | 差距 |
|---|---|---|---|
| 1000 文件 | ~200ms | ~30ms | 6.7x |
| 50000 文件(典型 node_modules) | ~5s | ~800ms | 6.3x |
| 200000 文件 | ~25s(内存压力大) | ~3s | 8.3x |
这个数据非实测,但和 jwalk 基准测试结果一致。对于 Quick Look 这种"每次空格都扫描"的场景,1.5 秒和 8 秒的差距就是"可用"和"不可用"的区别。
3.3 Swift ↔ Rust 的 FFI 方案推演
这是架构中最关键的信息缺口——SpacePeek 的桥接层源码不可见,但可以从 macOS 生态的标准做法反推。
三种主流方案:
| 方案 | 原理 | 优点 | 缺点 | SpacePeek 可能性 |
|---|---|---|---|---|
| UniFFI | Mozilla 的跨语言绑定生成器,从 Rust 代码自动生成 Swift 绑定 | 自动化程度高、类型安全、支持复杂数据结构 | 需要构建脚本、生成的 Swift 代码量较大 | 🟡 中等 |
| 手写 C ABI | Rust 侧用 extern "C" 导出函数,Swift 侧声明 @_silgen_name 调用 | 最直接、可控、无依赖 | 手动管理内存、字符串传递繁琐、容易出错 | 🟢 高 |
| cbindgen | 把 Rust 的 extern "C" 函数声明自动生成 C 头文件,Swift 通过模块映射调用 | 半自动、适合有经验的开发者 | 需要额外构建步骤 | 🟢 高 |
推断:最可能是手写 C ABI + cbindgen
对于一个独立开发者来说,UniFFI 的学习曲线较陡,且生成的代码量不适合一个小工具。更可能的是这个流程:
Rust 代码
└── [cbindgen] → C header (spacepeek_ffi.h)
│
Swift 代码 │
└── Bridging Header ←┘
│
├── func scan_folder_start(path: UnsafePointer<CChar>) -> OpaquePointer
├── func scan_folder_poll(handle: OpaquePointer) -> ScanStatus
└── func scan_folder_result(handle: OpaquePointer) -> UnsafePointer<CChar>
数据交换格式推断:
Rust 扫描的结果需要传回 Swift 做 UI 渲染。常见方案两种:
-
JSON 序列化:Rust 用
serde_json序列化扫描结果 → 传 JSON 字符串到 Swift → Swift 用JSONDecoder反序列化为 Swift struct。简单但有一定序列化开销。 -
C 结构体传递:定义 C 兼容的结构体,通过指针在两层之间传递。快但代码复杂。
对于 SpacePeek 的场景(扫描文件数通常在数千到数十万),JSON 序列化的开销相比于文件 I/O 本身可以忽略。推测用的是 JSON 方案——简洁、易 debug、且 Swift 侧的 Codable 反序列化随手可用。
关键代码示例(推断):
// Rust 侧:扫描入口 + JSON 输出
#[no_mangle]
pub extern "C" fn spacepeek_scan(path: *const c_char) -> *mut c_char {
let path = unsafe { CStr::from_ptr(path).to_str().unwrap() };
let result = scan_folder(Path::new(path));
let json = serde_json::to_string(&result).unwrap();
CString::new(json).unwrap().into_raw()
}
#[no_mangle]
pub extern "C" fn spacepeek_free_string(s: *mut c_char) {
unsafe { drop(CString::from_raw(s)); }
}
// Swift 侧:调用 + 反序列化
func scanFolder(at url: URL) -> ScanResult {
let path = url.path.cString(using: .utf8)!
let jsonPtr = spacepeek_scan(path)
let json = String(cString: jsonPtr!)
spacepeek_free_string(jsonPtr)
return try! JSONDecoder().decode(ScanResult.self, from: json.data(using: .utf8)!)
}
3.4 几个值得单独拎出来的设计决策
决策 1:Quick Look Extension 而非独立 App
| 独立 App | Quick Look Extension |
|---|---|
| 需要用户手动启动、切换窗口 | 无缝融入 Finder 操作流 |
| 可以做后台索引、菜单栏常驻 | 纯按需、零驻留 |
| 用户学习成本:打开 App → 选文件夹 → 点分析 | 用户学习成本:选文件夹 → 按空格 |
SpacePeek 的"零学习成本"正是 Quick Look Extension 这个形态赋予的。如果它是一个独立 App,功能再强大也无法形成肌肉记忆级别的使用频率。
决策 2:完全离线,不建索引
很多磁盘分析工具(DaisyDisk、GrandPerspective)需要首次全盘扫描建索引,之后增量更新。SpacePeek 不做索引——每次按空格都是"冷扫描"。
这个选择的代价是每次都要重新遍历,收益是不需要后台进程、不需要磁盘写入、不需要担心索引过期。这是一个架构上的取舍:用 Rust 的扫描速度来换取索引的复杂性。而 Rust 的确实把这个代价降到了用户可以接受的 1-2 秒。
决策 3:macOS 14.0+ 最低要求
限制 macOS 14.0 意味着放弃了 Ventura 及更早版本的用户。可能的理由:
-
Swift Charts framework(macOS 13+ 但更成熟的 API 在 14+)
-
SwiftUI 导航相关的改进(NavigationStack 等)
-
或者纯粹是为了减少测试矩阵
四、对照开源项目:PeekX 的纯 Swift 实现
为了理解 SpacePeek 的技术选择,我们对照一个功能类似但技术栈不同的开源项目:PeekX。
PeekX 的架构:
PeekX (MIT License, 开源)
├── PeekX.app → 启动后注册扩展,然后退出
├── PeekXExt.appex → Quick Look Extension
│ ├── PreviewViewController.swift → 实现 QLPreviewingController
│ │ ├── 用 FileManager.default.enumerator 遍历文件夹
│ │ ├── 统计文件数、类型分布
│ │ └── 用 WebKit 渲染预览界面(而非 SwiftUI)
│ └── Info.plist → 注册 public.folder 的预览支持
└── Shared/ → 应用和扩展共享的常量
PeekX vs SpacePeek 技术对比:
| 维度 | PeekX | SpacePeek |
|---|---|---|
| 界面渲染 | WebKit(HTML/CSS) | SwiftUI |
| 文件遍历 | FileManager.default.enumerator(Swift) | Rust 引擎(jwalk/rayon 推断) |
| 并发遍历 | 不支持(单线程 while let) | 支持(rayon 并行) |
| 存储分布图 | 无 | Swift Charts 饼图 |
| 开源 | ✅ MIT License | ❌ 闭源 |
| 定价 | 免费 | 免费 + $1.99 Pro |
关键差异:
WebKit 渲染 vs SwiftUI:WebKit 在 Quick Look Extension 中有一个天然问题——它启动比 SwiftUI 慢,且内存占用更高。但优点是跨平台可复用和样式灵活。SpacePeek 选 SwiftUI 是为了原生性能和更小的内存占用。
单线程遍历 vs 并行遍历:这是一个质变。对一个 5 万文件的文件夹,PeekX 的 FileManager.enumerator 需要约 5 秒,而 SpacePeek 的 Rust 并行方案约 0.8 秒。在 Quick Look 这种"每次按空格都重新扫描"的场景,这个差距就是可用性本身。
五、如果你是开发者:这个架构怎么复现
这一节是给有意自己实现类似工具的读者的。分三步:
Step 1:搭建 Quick Look Extension 骨架
# Xcode: File → New → Target → Quick Look Preview Extension
# 然后配置 Info.plist 的 QLSupportedContentTypes
# 验证扩展是否注册成功
pluginkit -m -v -p com.apple.quicklook.preview | grep YourApp
Step 2:集成 Rust 扫描库
# 在项目中创建 Rust 库
cargo init --lib scanner
# Cargo.toml 关键依赖
[dependencies]
jwalk = "0.8" # 并行目录遍历
serde = { version = "1", features = ["derive"] }
serde_json = "1"
rayon = "1" # 并行处理
[lib]
crate-type = ["staticlib", "cdylib"] # 同时生成 .a 和 .dylib
在 Xcode 的 Build Phases 中添加 Run Script,在编译前调用 cargo build --release,将产物链接到 App Extension target。
Step 3:Swift ↔ Rust 桥接
使用 cbindgen 自动生成头文件,最简单的方案:
# 在 Rust 项目目录
cbindgen --config cbindgen.toml --crate scanner --output ../YourApp/scanner_ffi.h
然后在 Swift 的 bridging header 中 #include "scanner_ffi.h"。
六、边界与局限性
本文的边界
-
❌ 本文是基于公开信息的架构推断,不是 SpacePeek 的官方实现说明
-
❌ 未对 SpacePeek 二进制进行反编译分析(
nm/strings),Rust crate 列表为推测 -
❌ 没有 SpacePeek 的 FFI 实现源码,桥接方案为行业标准做法的推断
这个架构的适用范围
| 适合 ✅ | 不适合 ❌ |
|---|---|
| macOS 工具类应用(需要 UI + 高性能计算) | iOS 应用(Quick Look Extension 不支持 iOS) |
| 文件系统密集型操作(扫描、索引、分析) | 网络密集型操作(Rust 的 async 生态在这里帮助不大) |
| 独立开发者项目(Rust + SwiftUI 组合学习曲线适中) | 团队项目中非 Rust 开发者参与维护 |
| App Store 分发(Rust 动态库可以随 .appex bundle 打包) | 需要 App Store 审核的应用(确保链接正确,否则被拒) |
如果这个工具不合适
如果你需要一个类似的文件夹预览工具但不想自己开发,替代品包括:
-
PeekX(开源,纯 Swift,MIT 协议)——适合 DIY 定制
-
Folder Preview 或 Folder Quick Look(App Store 免费下载)——开箱即用
-
或者直接用 SpacePeek——免费版已经解决 90% 需求
七、总结
三点核心
-
SpacePeek 的架构选型非常精准:Quick Look Extension(零学习成本)+ Rust 扫描引擎(毫秒级遍历)+ SwiftUI(原生渲染)。三个技术的组合不是因为"酷",而是各自解决了一个具体的架构约束。
-
Rust 在这里不是过度工程。在"每次按空格都要重新扫描整个文件夹树"的场景下,Swift 的
FileManager.enumerator在大文件夹上的表现是不可接受的。Rust 的并行遍历 + 批量系统调用是唯一能让这个体验成立的技术方案。 -
对照 PeekX 可以看到两条路:纯 Swift + WebKit(PaekX)适合快速原型和社区协作,SwiftUI + Rust(SpacePeek)适合追求极致性能和原生体验。没有优劣,只是场景不同。
关于作者
SpacePeek 由「府王」(Fu Wang)独立开发,2026 年 6 月上架 Mac App Store。本文不包含任何利益关联,纯粹从技术角度对其架构做了一次"侦探式"分析。
你还见过哪些"看起来简单但架构选型非常有料"的 macOS 小工具?欢迎在评论区分享——我们一起来拆解。
969



被折叠的 条评论
为什么被折叠?



