
Zipic 实录:一款独立开发产品的实战经验
做独立产品这件事,说起来容易,真动手了才知道水有多深。Zipic 从一个职场工作的小需求,到现在成为我一直在做的主力产品之一,中间踩过的坑、学到的东西,远比我预想的多。今天这篇文章想聊聊开发 Zipic 过程中一些真实的技术挑战和决策思考,记录分享给你,篇幅很长,收藏慢慢看!
嗨,大家好,我是十里👋!今天这篇文章想聊聊开发 Zipic 过程中一些真实的技术挑战和决策思考,不是教程,更像是创造记录。希望对同样在 macOS 平台折腾的创客们有点帮助。
一、从痛点到产品
在前公司做产品经理期间,日常工作离不开文档和 PPT,每份方案都要配大量截图和流程图。文件体积慢慢成了困扰——一份 PPT 动辄几十上百 MB,传文件要等,存几个版本就占掉好几百 MB,打开还卡顿。印象最深的是外包公司交付过一个 600+ MB 的 PPT,在我那台 256GB 的 MBP 上格外扎眼。
从"不爽"到"动手"
根源其实是图片。每张截图几 MB,塞进文档里体积自然爆炸。与其抱怨文档大,不如先把图片压一压。
找了几个工具,选定了图压,设计不错、效果也好。但用久了有些不满足:应用体积大(Electron 通病)、交互方式单一、支持格式有限。脑子里冒出念头:"要不自己做一个?"
琢磨了一阵后定位清晰了:与其事后压缩文档,不如从源头把图片处理好。Zipic 要做的就是图片压缩这个基础需求——更原生、更轻量、更灵活。
Zipic 的核心原则
- 小而美:专注图片压缩,把这一件事做好
- 原生体验:用 SwiftUI 开发,完全遵循 macOS 的设计规范
- 高效灵活:支持更多快捷的交互方式
- 格式丰富:支持更多常用的图像/文档格式
- 隐私安全:所有处理都在本地完成,不上传任何数据
虽然最后确定做图片压缩工具,但文档压缩功能一直在我的 Roadmap 里。计划 2.0 大版本会支持文档和视频压缩,让它变成一个从源头到最终优化的完整方案。
不过那是后话了。接下来聊聊做 Zipic 过程中遇到的一些问题和经验。
二、技术挑战篇:真实产品化的硬骨头
Zipic 最初是在 Mac App Store 上架的。App Store 分发的好处明显:不用操心更新机制,也不用自己搭建支付系统。但慢慢意识到一些问题:
- 沙盒限制太多:Mac App Store 要求应用必须开启沙盒,这出于安全考虑可以理解。但对于图片压缩工具来说,最明显的是文件读写权限——用户压缩完图片想保存到原位置,却发现没有权限。虽然可以通过 Security-Scoped Bookmark 持久化来解决目录授权,但管理复杂,体验也变繁琐。
- 审核周期不可控:提交更新至少要等 1~2 天才能上线,遇到节假日甚至超过一周。用户反馈的 bug 修复本来很快,却因为审核拖得很长。
- 分成比例偏低:默认 70%,申请小型企业计划最高 85%。
我想做的是体验流畅的工具,但 App Store 的限制让很多想法无法实现。加上后续希望上架其它平台(比如数码荔枝),总要搭建独立授权系统,权衡之后决定切换到独立分发。
如果不是万不得已,真心建议别折腾独立分发,你会变得非常不幸 😂 不如直接上架 App Store + 申请小型企业计划。
2.1 独立分发体系的构建
选择独立分发意味着要自己搞定打包、更新、授权、支付等整套体系。做好心理准备,这会占用很多精力,不符合快速 MVP 的原则。当然,这也是一劳永逸的工作,准备好了其他产品可以轻松复用。
2.1.1 DMG Canvas 打包流程
应用开发完成后要打包成 DMG 格式分发。用 Disk Utility 简单创建 DMG 效果比较粗糙,专业的应用 DMG 通常有漂亮的背景图、精心设计的布局、自动设置的窗口大小。手动制作很繁琐,更重要的是 macOS 的公证(Notarize)流程比较复杂,配置不对用户下载后会提示「文件已损坏」。
我用的是 DMG Canvas,所见即所得的编辑界面,可以自定义背景、图标位置、窗口样式。配置好模板后,每次打包只需选择新版本的 app 包重新 build 就行。公证流程它也做了集成,配置好 App-Specific Password 和 Team ID,勾选「Notarize」选项,打包完成后会自动上传到 Apple 服务器公证,整个流程自动化。
具体操作参考:使用 DMG Canvas 打包 macOS app 为 DMG。
2.1.2 使用 Sparkle 实现自动更新
做独立应用还要解决更新机制。用户下载安装后如何及时获取新版本?手动下载覆盖安装体验很差,还要考虑安全性、确保更新包没被篡改。
Sparkle 是 macOS 平台上最常用的自动更新框架,很多知名应用都在用。支持签名验证、增量更新、发布说明展示,基本该有的功能都有了。集成过程不算复杂,主要是配置 Appcast(更新信息文件)和签名密钥,添加少量代码就能实现自动更新。Appcast 是个 XML 文件,记录最新版本号、下载地址、更新说明等信息,Sparkle 定期检查这个文件,发现新版本就提示用户更新。
具体使用步骤写在这篇文章:macOS 开发 - 使用 Sparkle 轻松实现 App 更新管理
踩坑经验:文件托管我最终用了 CloudFlare R2。相比 GitHub Releases 或自建服务器,R2 的优势很明显——全球都能访问(包括国内),存储有限额但流量无限制,还可以自定义域名,既保证分发稳定性又控制成本。
另外,Sparkle 会自动生成 delta 包(增量更新文件),可以显著减少用户下载量。但要记得把这些 delta 文件一起上传到托管服务,不然增量更新会失效,目录结构要和 appcast.xml 中的路径一致。
2.1.3 授权系统
Zipic 用的是密钥验证激活方式,有免费版和 Pro 版,需要一个系统来管理许可证、验证用户购买状态。需求比较特殊:要支持多个销售平台(数码荔枝、官网商城等),每个平台的订单都要能自动生成授权码。
我选择了 Keygen.sh 的 CE(社区版)进行自部署。Keygen 本身是商业授权管理服务,但提供了开源的社区版本。功能基本够用,支持许可证生成、验证、使用统计等。自部署的好处是完全掌控数据,而且可以按需定制。用 Docker 部署在自己服务器上,配置好数据库和 Redis,系统就跑起来了。通过自实现 Webhook 服务对接各个电商平台,订单完成后自动生成授权码发给用户。
详细部署过程可以看这篇:软件授权用 Keygen.sh - 自部署服务
踩坑经验:Docker 网络配置是个坑。Keygen 需要连接数据库和 Redis,一开始用默认网络,服务启动后连不上数据库,后来建了自定义网络才解决。另外,如果直接用 Keygen 与第三方对接的话,可以通过产品 token 提供生成 License 的权限,尽量减少第三方的权限范围。
2.1.4 支付系统集成
独立分发还要解决一个关键问题:用户怎么付款购买?这块我踩过不少坑。
最开始使用的是 LemonSqueezy,功能很全——支付、密钥管理、联盟推广、邮件营销一站式搞定。但 2024 年被 Stripe 收购后,国人新账户提交申请基本不予理睬,这条路基本不推荐了。
网上有人提议用个人身份注册香港 Stripe 账号,技术上可行,但没有真实香港身份始终有合规风险,提现还需要香港银行卡,也不是长久之计。
最终我走了注册美国公司使用 Stripe 的路。过程一波三折:注册公司还算顺利,是通过 Stripe Atlas 完成的,然后开银行公户。很多人推荐水星银行,但我申请了几十次都失败,证明材料都是完整且准确的,但还是失败了,推测是姓名和某些敏感人物重名,最后注册申请空中云汇账户才办好了银行公户。注册企业 Stripe 还需要美国电话号码,我用的是 Ultra Mobile PayGo,月租仅 3 美元。
- 注册美国公司还有一种方式,在怀俄明州自助注册公司(约 39)
- 如果有条件的话,最好是办一些香港银行卡,推荐香港汇丰、中银香港、众安银行,港卡可以用于 Stripe 个人账号的提现、Paypal 提现(服务费很低)、Stripe 绑定的银行公户收益回流等
据我了解,不同身份适合不同方案:
如果对 Paddle 感兴趣,可以参考:两天内完成 Paddle 账户的注册和验证
还有一种思路:不自建支付,直接对接销售平台。比如数码荔枝,只需实现密钥分发接口就行。国外也有类似平台,如 Setapp(目前只接受 AI 应用)和 AppSquad,感兴趣可以了解。
2.1.5 打通支付与密钥分发
有了 Stripe 处理支付、Keygen 管理授权,但问题是这两个系统是独立的。用户付完款,还得手动去 Keygen 生成许可证再发邮件——这流程显然不能接受,必须打通自动化。
我的方案是搭一个 Webhook 服务作为中间层(用 n8n 搭建应该也行😉)。核心思路是建立 Stripe 产品与 Keygen 授权策略的映射关系。比如「Zipic Personal - 1 Device」在 Stripe 是一个价格 ID,在 Keygen 对应一个特定的 Policy,服务里配置好这个对应关系。然后在 Stripe 后台注册 Webhook 地址,订阅 checkout.session.completed 等支付完成事件。
用户完成购买后,Stripe 立即向服务发送通知,服务收到后自动完成:
1️⃣ 在 Keygen 中查找或创建用户(基于邮箱) 2️⃣ 根据购买的产品,调用 Keygen API 生成对应的许可证 3️⃣ 通过 Resend 发送邮件,包含许可证密钥和激活说明
整个过程对用户无感,付款成功后几秒钟邮箱就收到许可证了。
踩坑经验:Webhook 幂等性很重要。Stripe 为保证消息送达,同一事件可能发送多次,不做处理用户可能收到多封邮件甚至生成多个许可证。解决方案是记录已处理的事件 ID,重复请求直接跳过。邮件发送也要考虑失败重试,我加了重试队列,同时所有发送记录入库方便排查和手动补发。
2.2 UI 上的一些细节
2.2.1 macOS 26 中隐藏主窗口标题栏
Zipic 的界面设计追求简洁,不需要传统的标题栏。在早期版本中,用 .windowStyle(.hiddenTitleBar) 就能完美隐藏标题栏,一切正常。
升级到 macOS 26(Tahoe)后,问题来了。标题栏确实「隐藏」了,但留下了一块白色的背板区域,像是标题栏的「幽灵」还在那里占位,整个窗口顶部多了一条不协调的留白。更麻烦的是,这个问题在 Xcode 预览模式下看不出来,预览里一切正常,真机运行才会发现,意味着每次调试都要编译运行。
查阅资料后发现,macOS 26 引入了新的窗口背景系统。原来的 .hiddenTitleBar 只是隐藏了标题栏的 UI 元素,但没有处理底层的容器背景。新的解决方案是使用 .containerBackground(.clear, for: .window) 修饰符:
这个修饰符告诉系统窗口的容器背景应该是透明的,配合 .hiddenTitleBar 终于实现了完全无标题栏的效果。
具体适配细节可以参考:macOS 开发 - macOS 26 中隐藏主窗口标题栏
踩坑经验:如果应用使用了 NavigationSplitView,还需要额外处理侧边栏的标题。默认情况下侧边栏会显示一个 inline 样式的导航标题,也需要隐藏:
还有一个细节是窗口拖拽——隐藏标题栏后用户怎么拖动窗口?我的做法是通过 NSWindow 的 isMovableByWindowBackground 属性,或者在特定区域添加自定义的拖拽手势,既保持简洁界面又不影响用户操作。另外,.containerBackground 是 macOS 15+ 的 API,如果需要兼容更早的系统版本要做好条件判断。
2.2.2 文件大小显示与 Finder 保持一致
有用户反馈:「Zipic 显示的文件大小和 Finder 里看到的不一样,是不是压缩有问题?」
这让我很困惑,文件大小就是用 FileManager 获取的 fileSize 属性,怎么会不对?仔细对比后发现差异确实存在,比如一个文件 Finder 显示 1.26 MB,Zipic 显示 1.2 MB。虽然差别不大,但对于一个「精确压缩」的工具来说,这种不一致会让用户产生疑虑。
问题出在「文件大小」这个概念本身。文件系统中大小有两种:
- 逻辑大小(Logical Size):文件实际包含的数据量
- 物理大小(Physical Size):文件在磁盘上占用的空间
Finder 默认显示的是逻辑大小,而且使用的是 1000 进制(1 KB = 1000 bytes),而不是程序员习惯的 1024 进制。要获取与 Finder 一致的文件大小,需要用 URL 的 resourceValues API:
格式化显示时,要用 ByteCountFormatter 并指定正确的选项:
关键是 .countStyle = .file,这会让格式化器使用 macOS 文件系统的标准方式来显示大小。
详细实现可以参考:macOS 开发 - 获取与 Finder 一致的文件大小
踩坑经验:有几个特殊情况需要注意:
- 稀疏文件:某些文件的逻辑大小和物理大小差异很大,比如虚拟机磁盘文件逻辑上可能有 64GB,但实际只占用几个 GB。
- APFS 透明压缩:APFS 文件系统支持透明压缩,一个文件可能逻辑上是 10MB,但因为系统自动压缩实际只占 3MB,这种情况下「节省空间」的计算会变得复杂。
- 符号链接:如果文件是符号链接,获取的可能是链接本身的大小而不是目标文件的大小,需要用
.resolvingSymlinksInPath()先解析真实路径。
我的处理策略是统一使用逻辑大小作为显示标准,和 Finder 保持一致。修复后再也没收到类似反馈。
2.2.3 没有设计稿,直接写代码
Zipic 的 UI 有个「秘密」:从来没有画过设计稿。
不是因为懒,而是发现对于工具类应用,脑子里有清晰的设计原则比画设计稿更高效。Zipic 的 UI 严格遵守《写给大家的设计书》里的四个基本原则:亲密性、对齐、重复、对比。这四个原则简单易懂,却是让 UI 提升一个台阶的最简单可行的方式。
亲密性(Proximity):相关的元素放在一起。Zipic 的压缩列表里,每个文件项的缩略图、文件名、大小、压缩率紧挨着,形成一个视觉单元;而不同文件之间有明显的间距。用户扫一眼就能分清哪些信息属于同一张图片。设置面板也是,按功能分组——输出设置放一块,质量设置放一块,不会把「输出路径」和「压缩质量」混在一起。
对齐(Alignment):所有元素都要有视觉上的连接。Zipic 的侧边栏、列表视图、详情面板,相同元素内所有文字都有对齐。看起来是小事,但如果对齐乱了,整个界面会显得「业余」。SwiftUI 的 alignment 参数用得很多,.leading、.trailing、.center 要根据内容类型选择。
重复(Repetition):统一的视觉元素贯穿始终。Zipic 里的圆角半径是统一的,所有可点击的文字都用同一种颜色。图标风格也统一——要么都用 SF Symbols,要么都用自定义图标,不混搭。这种一致性让用户觉得「这是一个完整的产品」,而不是「拼凑出来的」。
对比(Contrast):重要的东西要突出。压缩率是用户最关心的数据,所以用蓝色渐变标签显示;文件名次要,用常规字体;原始大小更次要,用浅灰色。主次分明,用户不用思考就知道该点哪个。
这四个原则听起来简单,但真正用好需要不断调整。我的做法是:写完代码先跑起来看效果,觉得哪里「不对劲」就用这四个原则去检查。是不是相关元素没有靠近?是不是对齐出了问题?是不是重复的元素不够统一?是不是该突出的没突出?
没有设计稿的好处是迭代快——改几行代码就能看到效果,不用先改设计稿再改代码。当然这种方式有个前提:脑子里要有清晰的设计原则,不然很容易改着改着就乱了。
2.3 核心功能的技术实现
接下来分享几个核心功能的实现细节,这些都是在 macOS 平台上比较有意思的技术点。
2.3.1 文件夹监控自动压缩
文件夹监控是 Zipic 的特色功能:用户指定一个文件夹,Zipic 实时监控它的变化,一旦有新图片添加进来就自动压缩。需求看着直白,实现时遇到了不少问题:监控机制怎么选、事件爆发怎么处理、输出文件触发无限循环怎么办……
首先是监控机制的选择。最直观的方式是轮询——每隔几秒扫描一次目录看有没有新文件。但这种方式太耗资源了,尤其是当用户监控的文件夹里有成千上万个文件时。最终选择了 macOS 提供的 DispatchSource.FileSystemEvent,这是基于内核级 kqueue 的高效监控机制,只在文件系统真正发生变化时才会触发,CPU 占用极低。
接下来遇到的问题是事件爆发。用户一次性拖入几百张图片,系统会在瞬间触发几百个事件。如果每个事件都立即处理,不仅效率低,还可能导致界面卡顿。解决方案是实现防抖机制(Debounce):当连续收到多个事件时,等待一小段时间(默认 0.5 秒),把这段时间内的事件合并成一次处理。这样用户拖入 100 张图片,最终只会触发一次压缩任务。
最棘手的问题是无限循环。想象一下:用户监控文件夹 A,Zipic 检测到新图片 photo.jpg,压缩后生成 photo_compressed.jpg。但这个输出文件也在文件夹 A 里,于是又触发了监控事件……如此往复,无限循环。
为此我设计了预测性忽略机制。当检测到输入文件时,系统会预测输出文件的路径,提前加入忽略列表:
过滤系统也很重要。用户可能只想压缩特定类型的图片,或者只处理大于某个尺寸的文件。我采用了谓词模式来实现灵活的条件组合:
还有一些细节值得注意。监控深度要根据实际场景设置——对于大多数用户,监控一两层子目录就够了。如果设置成无限深度,遇到复杂的目录结构(比如 node_modules),会创建大量 watcher,占用过多系统资源。另外,新创建的子目录也需要动态加入监控,这要求系统在检测到目录变化时,自动检查是否有新的子目录并为它们创建 watcher。
这套方案经过反复打磨后,后来我把核心部分开源成了 FSWatcher 项目,希望能帮到有类似需求的开发者。
2.3.2 PDF 压缩:利用 macOS 原生能力
PDF 作为日常文档中最常见的格式,很多时候里面就是塞满了图片。支持 PDF 压缩是用户呼声很高的需求。
PDF 压缩和普通图片压缩完全是两码事。图片压缩相对简单——读取像素数据,用算法重新编码,写入新文件。但 PDF 是复杂的容器格式,里面可能包含文本、矢量图形、嵌入字体、书签、表单……当然还有图片。关键问题是:如何在不破坏 PDF 结构的前提下,只对其中的图片进行压缩?
最初考虑过几种方案:Ghostscript 功能强大但体积也大,需要处理外部依赖的分发问题;ImageMagick 对 PDF 结构的保留不够好;自己解析 PDF 格式手动提取和重压缩图片——这条路想想就知道是个无底洞。
最终选择了 macOS 原生的 Quartz Filter 技术,说实话有点相见恨晚。它就藏在系统里专门干这个事,系统自带的「预览」应用导出 PDF 时的「减小文件大小」选项用的就是这个。工作原理很优雅:在渲染 PDF 页面时自动对其中的位图图像进行 JPEG 重压缩,而文本、矢量图形、字体这些保持原样不动。
这是 macOS 平台独有的优势——Core Graphics 框架有硬件加速支持,处理速度很快,而且不需要打包任何外部依赖,应用体积不会膨胀。
核心思路是动态生成自定义的 .qfilter 配置文件定义压缩参数,然后通过 Core Graphics 框架应用这个滤镜来渲染 PDF。整个流程:根据用户选择的压缩等级生成对应的 Quartz Filter 配置文件 → 读取源 PDF → 创建新的 PDF 上下文并应用 Filter → 逐页渲染(这一步 Filter 会自动处理图像压缩)→ 输出压缩后的 PDF。
关键代码(Quartz Filter 文件生成):
压缩等级的设计上,我定义了 6 个级别,对应不同的 JPEG 质量参数(0.9 到 0.2)。值得注意的是,JPEG 压缩到 0.2 这个级别图片会出现明显的压缩痕迹,对于需要打印或展示的文档建议用较高的质量级别,如果只是用于网络传输或归档可以激进一些。
关键代码(PDF 压缩核心逻辑):
关键代码(页面渲染实现):
踩坑经验:动态生成的 .qfilter 文件需要存放在合适的位置(我选择了 ~/Library/zipic/filters/),要处理好文件不存在或损坏的情况,必要时重新生成。
另外要注意的是,Quartz Filter 只压缩 PDF 中的位图图像,如果一个 PDF 主要是文字和矢量图形,压缩后体积可能变化不大——这一点需要在产品层面向用户说明,避免「为什么压了半天没变小」的困惑。好消息是 PDF 中的元数据(书签、链接、表单等)在这个方案下都能完整保留,这是很多第三方方案做不到的。
2.3.3 缩略图生成优化
Zipic 的列表视图需要展示大量图片缩略图。最初的实现很朴素:用 AsyncImage 直接加载原图,让系统自动缩放显示。小图没问题,但遇到大图就明显卡顿,内存也跟着飙升。
第一次优化:预生成缩略图缓存
思路很直接——既然每次都要缩放,不如预先生成好缩略图存到磁盘,下次直接加载小图。早期实现用的是 NSImage 的 lockFocus/unlockFocus 方法:
问题是:这种方式需要先把原图完整解码到内存,再缩放绘制。一张 8000×6000 的图片解码后占用约 192MB 内存,批量处理时内存轻松飙到几个 GB。而且 lockFocus 已被 Apple 标记为废弃 API,在后台线程调用还可能触发优先级倒置。
第二次优化:CGContext 手动绘制
为了解决 lockFocus 的问题,改用 CGContext 手动创建位图上下文:
解决了线程安全问题,但本质没变——依然要先完整解码原图才能获取 CGImage,内存问题还在。
最终方案:ImageIO 下采样
后来发现 ImageIO 框架提供了 CGImageSourceCreateThumbnailAtIndex 这个 API,可以直接从图片文件生成指定尺寸的缩略图,内部采用渐进式下采样,根本不需要完整解码原图。这才是正解。
效果对比:同样处理 8000×6000 的 JPEG,完整解码约 192MB,下采样生成 192px 缩略图约 0.15MB——内存节省近千倍。
关键参数说明:
kCGImageSourceShouldCache: false:创建图像源时禁用缓存,避免原图数据驻留内存kCGImageSourceShouldCacheImmediately: true:立即在当前线程解码,防止被调度到低优先级后台线程导致优先级倒置kCGImageSourceCreateThumbnailWithTransform: true:自动应用 EXIF 方向信息,手机拍的竖屏照片不会「横着」显示kCGImageSourceThumbnailMaxPixelSize:指定最大边长,ImageIO 内部会按需下采样
踩坑经验:
- 内存释放:批量处理时用
autoreleasepool包裹循环体,确保每张图处理完立即释放临时对象,避免内存累积 - 同名文件冲突:早期缩略图只用文件名存储,不同目录下的
photo.jpg会互相覆盖。解决方案是缓存目录保留相对路径结构,比如a/photo.jpg和b/photo.jpg的缩略图分别存到thumbnails/a/photo.png和thumbnails/b/photo.png
这个优化的核心原则是:对于只需展示缩略图的场景,永远不要完整解码原图。ImageIO 的下采样是 Apple 官方推荐的最佳实践。
2.3.4 设备指纹稳定性优化
一度收到用户反馈「没有换电脑却需要重新激活」——排查后发现旧版指纹算法包含了会变化的主机名,网络环境变化或系统更新都可能让 hostname 改变,导致指纹漂移从而触发误判。
解决方案是设备指纹只依赖稳定的硬件标识,移除任何可变的软标识,最终仅保留主板序列号与以太网 MAC 地址参与计算:
踩坑经验:
- 静默迁移策略:验证失败时自动回退用旧指纹再试,若旧指纹有效则后台注销旧设备并以新指纹重新激活,全流程用户无感知
- 网络错误容错:仅在明确的业务错误码下才注销授权,对超时、网络抖动等异常只记录日志与重试,不改变本地授权状态
2.3.5 批量压缩并发优化
当用户一次性拖入几百张图片时,如何高效处理是个技术挑战。最初的串行方案——一张一张压缩——显然太慢了,但简单地开满并发也会带来问题:系统卡顿、内存飙升、任务调度混乱。
最终方案是 OperationQueue + DispatchGroup 的组合,配合 QueueManager 实现双队列负载均衡调度。
为什么是双队列?
单队列的问题很明显:用户先拖入 500 张大图,队列被占满;紧接着又拖入 3 张小图想快速处理,却要排在 500 张后面等待。双队列设计让新任务可以分配到负载较轻的队列,小批量任务不会被大批量任务阻塞。
关键代码(QueueManager 负载均衡分配):
关键代码(批量压缩流程):
为什么选择 OperationQueue 而非 GCD?
OperationQueue 相比原生 GCD 有几个关键优势:支持任务取消(用户清空列表时需要)、可以设置最大并发数、能查询任务状态。这些特性对于需要精细控制的批量处理场景非常重要。
踩坑经验:
- 线程安全:多个 Operation 会并发更新进度统计,需要用
DispatchSemaphore保护共享状态避免数据竞争 - QoS 设置:必须设置
queue.qualityOfService = .userInitiated,否则系统会有优先级警告且任务可能被降级执行 - 并发数选择:默认 8 是经验值。压缩是 CPU 密集型操作,过高的并发数会导致上下文切换开销增大,过低则 CPU 利用率不足。
- UI 更新:Operation 内部的 UI 更新必须 dispatch 到主线程,否则会有线程安全问题和警告
- 多批次进度追踪:用户可能连续拖入多批图片,每批任务需要独立追踪进度,总进度是所有批次的加权平均。我设计了一个
TaskStack来管理这种场景
2.3.6 第三方扩展能力:URL Scheme 实践
工具类产品要真正融入用户的工作流,光靠自己的界面是不够的。用户可能在用 Raycast、Alfred、快捷指令,甚至自己写脚本和产品——如果 Zipic 能被这些工具调用,使用场景会宽广很多。
macOS 上跨应用通信最通用的方式是 URL Scheme。在 Info.plist 中注册自定义 scheme 后,任何应用都可以通过 open "zipic://..." 拥有 Zipic 的能力。
URL 格式设计为 zipic://compress?url=路径1&url=路径2&level=3&format=webp,支持传入多个文件路径和压缩参数。
Raycast 扩展:URL Scheme 的最佳实践
基于这套 URL Scheme,我开发了 Raycast 扩展,目前已有 2000+ 安装量。用户的典型工作流是:在 Finder 选中图片/文件夹 → 按快捷键呼出 Raycast → 输入 compress 回车 → 完成,整个过程不到 2 秒。 如果 扩展的命令设定快捷键,工作流就简化了:Finder 选中图片/文件夹 → 按压缩命令的快捷键 → 完成。
扩展做的事情很简单:获取 Finder 选中的文件路径,编码成 JSON,拼成 URL,然后 open。Zipic 端收到请求后解析路径执行压缩,完成后发送系统通知。
踩坑经验:
- 路径编码:文件路径可能包含空格、中文等特殊字符,必须做 URL 编码,否则解析会出错
- 安全校验:收到的路径要验证文件存在、是支持的图片格式、不是
.app或.bundle等特殊目录,防止恶意调用
除了 URL Scheme,Zipic 也支持 macOS 13+ 的 AppIntents,可以直接在快捷指令中调用,未来 Apple Intelligence 也能识别。但 URL Scheme 的优势是兼容性好、集成简单,对于 Raycast/Alfred 这类效率工具是最佳选择。
三、产品迭代中的决策思考
代码写得再漂亮,用户用起来不顺手也是白搭。Zipic 从第一版到现在经历了无数次迭代,每一次改动背后都有用户反馈在驱动。
3.1 UI/交互优化的迭代过程
第一版 Zipic 的界面我自己觉得挺好的——简洁、干净、macOS 风格。但上线后很快收到反馈:「图片压缩前后的对比在哪里看?」「怎么知道压缩效果好不好?」
我当时的设计是只显示压缩后的文件大小和压缩率,觉得数字已经足够说明问题了。但用户不这么想,他们想亲眼看到画质有没有损失 😅
用户反馈的收集渠道:应用内反馈入口、Twitter/X 关键词监控。负面反馈往往最有价值。
3.1.1 对比视图:给用户「确定感」
用户反馈「想看压缩效果」,表面是功能需求,深层是心理需求——他们需要确认「压缩没有毁掉我的图片」。
压缩率 85%、节省 2MB 这些数字是理性信息,但用户的担忧是感性的:「画质真的没变差吗?」数字无法回答这个问题,只有亲眼看到才能安心。
所以我加入了对比视图:左右滑动查看压缩前后差异,支持缩放到像素级对比。技术上不复杂,就是两张图片叠加用一个可拖动的分割线控制显示区域。
产品决策上有个权衡:对比视图需要同时加载原图和压缩图,会影响处理速度。最终选择默认关闭但易于开启——不影响追求效率的用户,但需要确认的用户随时可以查看。这种「渐进式披露」的思路在很多场景都适用:核心流程保持简洁,高级功能按需展开。
3.1.2 多种压缩入口:融入用户已有的工作流
最初 Zipic 只支持拖拽到主窗口、选择文件、拖拽到 Dock 图标这些基础操作。用起来没问题,但总觉得「还不够」。
后来想明白了:问题不在功能本身,而在于用户必须中断当前工作来使用工具。写博客时想压缩截图,得先打开 Zipic,再把图片拖进去——这个「切换成本」虽然只有几秒,但足以打断心流。
解决思路是:不要让用户适应工具,而是让工具融入用户已有的工作流。于是陆续加入了:
- 文件夹监控:设置好监控目录后新图片自动压缩,用户甚至不需要主动操作
- 剪贴板压缩:复制图片后直接粘贴即可压缩,截图工作流完全不用中断
- Notch 拖放:把文件拖到刘海屏的 Notch 区域直接触发压缩,不需要先打开窗口
这些入口让 Zipic 从「需要专门打开的工具」变成了「随时可调用的助手」。设计工具类产品时,值得思考:用户在什么场景下需要这个功能?能不能在那个场景里直接触发,而不是让用户绕一圈?
3.1.3 预设功能:减少重复决策
早期版本每次压缩都要手动选质量和格式。功能上没问题,但用久了会发现:90% 的情况我都选同样的配置,为什么每次都要重新选一遍?
这是个认知负担的问题。每次选择都消耗一点注意力,虽然单次微不足道,但累积起来会让工具变得「累」。
预设功能的本质是把重复的决策固化下来。用户只需要思考一次「我通常需要什么配置」,之后就是一键完成。这个思路延伸一下:任何高频且模式固定的操作,都值得考虑提供「记住我的选择」的能力。
3.1.4 欢迎窗口:展示而非告知
1.8.0 版本加入了欢迎窗口(Onboarding),解决的是功能发现问题。
Zipic 的功能越来越丰富,但数据显示很多用户只用基础的拖拽压缩,Notch 拖放、文件夹监控这些主打功能反而没人知道。功能做了但用户不知道,等于没做。
用户很少会认真看文字说明。原因很简单:阅读是有成本的,用户打开一个工具是想完成任务,不是来学习的。
我的思路是:展示而非告知。与其用文字解释「你可以把文件拖到 Notch 区域」,不如直接用动画演示这个操作。用户一眼就能 get 到「原来还能这么用」,不需要理解任何文字。
技术上选择 Lottie 作为动画格式——文件体积小、支持矢量缩放、可精确控制播放。每个功能点做了一个循环动画,展示具体的操作手势和反馈效果。欢迎窗口分四步:欢迎页 → 依赖安装(自动完成)→ 功能轮播 → 完成页。用户可以点击指示器跳转,不用非得按顺序看完——尊重用户的时间。
有个细节:动画做了亮色/暗色两套主题适配,根据系统外观自动切换。工作量翻倍,但视觉一致性很重要,用户不应该在暗色模式下看到一个刺眼的亮色动画。
上线后有用户说「原来 Zipic 还能文件夹监控自动压缩,太方便了」——这正是我想要的效果。功能的价值只有在被使用时才能体现。
3.2 功能优先级的权衡
3.2.1 免费版 vs Pro 版的边界
这个问题纠结了很久,定价策略直接影响用户转化和产品口碑。最终的划分逻辑是:免费版覆盖核心需求,Pro 版提供效率提升。
- 免费版:基础压缩、常见格式(JPEG、PNG、WebP、HEIC)、有限格式转换、每天 25 张限制
- Pro 版:无限批量、更多格式(AVIF、TIFF、PDF、GIF)、完整格式转换、文件夹监控、剪贴板压缩、Notch 拖放、无限预设等
这样划分的考虑是:偶尔压缩几张图的用户免费版完全够用,不会觉得被限制;而重度用户(设计师、博主、开发者)会很自然地为效率付费。
3.2.2 功能丰富度 vs 产品复杂度
每增加一个功能界面就复杂一分,我的原则是:宁可少做,不要做成四不像。
举个例子,有用户建议加入「图片裁剪」功能。需求合理吗?合理。但加进来后 Zipic 就从「压缩工具」变成「图片编辑工具」了,定位会模糊。最终没有采纳,而是推荐用户用系统自带的预览 app 裁剪后再压缩。
类似被我拒绝的需求还有:上传图床、图片拼接……这些都是好功能,但不属于 Zipic 的边界。
3.3 性能优化的实践
性能优化是持续的过程,这里说几个关键点。
3.3.1 内存管理
图片压缩是内存密集型操作。一张 8000×6000 的照片解码到内存要占用近 200MB,同时处理 10 张这样的图片内存轻松飙到几个 GB。
解决方案是流式处理 + 及时释放——不是先把所有图片加载到内存,而是处理完一张立即释放再加载下一张。缩略图预览则用前面提到的下采样技术,只加载所需分辨率。
3.3.2 进度反馈
批量处理时用户最怕的是「假死」——不知道程序在干什么只能干等。我的做法是提供整体进度条(已完成/总数)和预估剩余时间(根据已处理文件的平均耗时动态计算),让用户对整个过程有清晰的预期。
四、做独立产品的心得体会
回顾 Zipic 从想法到产品的整个过程,感慨挺多的。这一路走来有兴奋、有焦虑、有成就感,也有不少「早知道就……」的时刻。
4.1 技术选型的思考
做 Zipic 之前我也纠结过:要不要用 Electron 或者 Tauri 这类跨平台方案?一套代码多平台运行听起来很诱人。
但最终还是选择了 Swift + SwiftUI 原生开发。原因很简单:我要做的是「macOS 上最好用的图片压缩工具」,而不是「能在多个平台勉强运行的工具」。
原生开发的优势很明显:应用体积小(Zipic 只有 12MB 左右),启动速度快,内存占用低,和系统的融合度高。SwiftUI 的声明式语法写起来也很舒服,配合 Xcode 的预览功能 UI 开发效率挺高的。
当然 SwiftUI 也有它的坑——在复杂场景下性能不如 AppKit,有些系统 API 还是得用 AppKit 桥接。Zipic 的主窗口最终就是用 AppKit 重构的,才解决了一些边界情况的问题。
我的建议是:如果你的目标用户主要在一个平台,原生开发值得考虑。跨平台方案适合快速验证想法,但要做到极致体验,原生还是有不可替代的优势。
4.2 产品与技术的平衡
作为开发者很容易陷入「技术炫技」的陷阱——看到一个新框架很酷就想用上,看到一种新架构很优雅就想重构。但用户不关心你用了什么技术,他们只关心:这个工具能不能帮我解决问题?用起来顺不顺手?
用户反馈驱动开发,而不是技术兴趣驱动开发。每个功能上线前问自己:这个真的是用户需要的吗?还是只是我觉得「应该有」?
4.3 独立开发的挑战
独立开发最大的挑战不是技术,而是一个人要扮演太多角色:产品经理、UI 设计师、前端开发、后端开发、运营、客服……时间管理变得特别重要,我的做法是按周规划,每周只聚焦 2-3 个核心任务,那些「可以做但不紧急」的事学会放到下一周甚至下个月。
营销是技术人最容易忽略的短板,这是一门大学问。很多开发者(包括曾经的我)觉得「产品好自然有人用」,但可能会变成 好酒也怕巷子深。市面上同类产品太多了,大多数用户根本没机会发现你,好的产品也可能看到的人很少。
我的体会是:在不擅长营销的时候,可以立马做的就是尽可能增加曝光。今年黑五我从 11 月中旬就开始准备,在 GitHub 上各种黑五优惠合集仓库提交 PR,最终成功提交了 11 个站点。效果很明显:Zipic 和 Orchard 这次黑五 90% 以上是海外订单,两款产品加起来的销售额是去年 Zipic 黑五活动期间的两倍多。
出海的话,Reddit 也值得重视。去年黑五的订单大部分来自我在 r/macapps 发的一个帖子,一个帖子带来的转化很多,也可认真评论帖子自荐产品;Reddit 也是发掘用户需求的好地方。
说到底,有收入才能可持续。技术再好、产品再精致,卖不出去就是自嗨。独立开发不是纯粹的技术活,学会营销、学会推广,是必修课(小学生艰难修行中🫠)。
还有一点:要学会接受「不完美」。初期版本可能有瑕疵,但先发布、收集反馈、快速迭代,比憋一个「完美版本」更有效。
4.4 开源社区的力量
Zipic 能做出来离不开开源社区的贡献。Sparkle 解决了自动更新,Keygen.sh 解决了授权系统,mozjpeg、pngquant、libwebp 这些库提供了核心的压缩能力……没有这些「巨人的肩膀」,一个人根本不可能做出这样的产品。
所以我也尽量回馈社区。文件夹监控那套方案打磨成熟后,我把核心部分开源成了 FSWatcher。虽然 star 不多,但还是挺开心的。
4.5 对未来的展望
Zipic 的 Roadmap 里还有很多想做的事:视频压缩、文档压缩(终于要把最初的想法捡回来了)……一步一步来吧。
对于同样想做独立产品的朋友,可以试着 先从解决自己的问题开始。日常遇到的痛点很可能也是别人的痛点。从小需求切入,做一个「小而美」的工具,比一上来就想做一个「大而全」的平台更容易有正向反馈。Zipic 是我辞职做独立开发后的第一款产品,它让我验证了这条路是可行的。
最后,感谢每一位使用 Zipic 的用户。你们的反馈、建议、甚至吐槽,都是我继续前进的动力。
"优雅而高效地解决用户问题,才是产品的价值所在。"
如果你对 Zipic 感兴趣,可以访问 官网 下载试用,docs.zipic.app 有详细的帮助说明。有任何问题或建议,欢迎随时联系我,站内留言我也会及时看到😉!



