前言
作为独立开发者,我做了一个 GPS 轨迹记录应用「雁过留痕」,上线 100 天,服务器费用为零——因为压根没有服务器。
这不是偷懒,而是一个从第一天就确定的架构决策:所有数据只存在用户本地设备上,没有账号体系、没有云端同步、没有任何第三方数据采集。
本文聊聊这个 Local-First 架构在实际 iOS 项目中的落地思路、遇到的坑,以及作为独立开发者在「零遥测」条件下如何迭代产品。
为什么选择纯本地架构
GPS 轨迹数据的敏感程度常常被低估。一条连续的位置记录可以还原一个人每天几点出门、去哪上班、周末在哪消磨时间。这比聊天记录更能勾勒出一个人的真实生活。
市面上绝大多数轨迹类应用默认把数据上传到云端,理由通常是「方便多设备同步」或「防止数据丢失」。但对于我的目标场景——Citywalk 记录、城市探索——用户更在意的是「我的行踪不要被别人知道」。
所以架构上做了一个极端选择:
┌─────────────────────────────┐
│ 用户 iPhone │
│ │
│ ┌───────────┐ ┌────────┐ │
│ │ CoreData │ │ GPX/ │ │
│ │ (轨迹点) │ │ GeoJSON│ │
│ └───────────┘ └────────┘ │
│ ▲ │
│ │ │
│ ┌──────┴──────┐ │
│ │ CLLocation │ │
│ │ Manager │ │
│ └─────────────┘ │
└─────────────────────────────┘
↕ 网络请求 = 0
没有后端,没有 Firebase,没有 Analytics SDK。整个 App 的网络权限都没申请。
技术实现要点
1. 智能省电:运动状态检测
纯 GPS 后台持续定位是电量杀手。我的方案是利用 CMMotionActivityManager 来判断用户当前是否在移动:
let motionManager = CMMotionActivityManager()
motionManager.startActivityUpdates(to: .main) { activity in
guard let activity = activity else { return }
if activity.stationary {
// 用户静止 → 降低定位频率或暂停 GPS
LocationService.shared.enterLowPowerMode()
} else if activity.walking || activity.cycling {
// 用户在移动 → 恢复精确定位
LocationService.shared.enterTrackingMode()
}
}
静止时自动关闭 GPS,移动时恢复记录。这样全天后台运行,电量消耗也能控制在可接受范围内。
2. 本地数据持久化
轨迹点使用 CoreData 存储,每条轨迹记录包含:
struct TrackPoint {
let latitude: Double
let longitude: Double
let altitude: Double
let timestamp: Date
let horizontalAccuracy: Double
let speed: Double
}
为了在地图上实现「像素风格」的覆盖效果,会对轨迹点做网格化处理,将经纬度映射到固定大小的格子里:
func gridIndex(for coordinate: CLLocationCoordinate2D, zoomLevel: Int) -> GridCell {
let gridSize = 360.0 / pow(2.0, Double(zoomLevel))
let x = Int(floor((coordinate.longitude + 180) / gridSize))
let y = Int(floor((coordinate.latitude + 90) / gridSize))
return GridCell(x: x, y: y)
}
这也是「像素地图」视觉效果的底层逻辑——用户走过的区域会以像素格子的形式被点亮。
3. 城市成就系统
没有服务器不代表不能做趣味功能。城市边界数据以 GeoJSON 格式内置在 App Bundle 中,当用户的轨迹点首次落入某个城市的多边形范围内时,触发「解锁」:
func checkCityUnlock(point: CLLocationCoordinate2D) {
for city in preloadedCities where !city.isUnlocked {
if city.polygon.contains(point) {
city.unlock(at: Date())
NotificationCenter.default.post(name: .cityUnlocked, object: city)
}
}
}
像一个只属于自己的探索游戏,没有排行榜、没有社交比较,纯粹记录你去过的地方。
零遥测下的产品迭代困境
说实话,这个架构对开发者非常不友好。
没有 Crashlytics,崩溃日志只能通过 Xcode Organizer 看到用户主动同意上传的系统级报告(量极少)。没有 Mixpanel/Amplitude,完全不知道用户在哪个页面停留最久、哪个功能没人用。没有漏斗分析,不知道新用户引导流程在哪一步流失。
等于闭着眼睛做产品。
我的应对策略:
- App Store 评价是唯一反馈渠道 —— 每条评论都认真看
- TestFlight 内测群 —— 愿意聊的用户比数据埋点更有价值
- 自己重度使用 —— 每天用自己的 App 记录 Citywalk,dogfooding 发现问题
- 小步发版 —— 既然不能 A/B test,就快速发版然后看评分变化
100 天的数据
| 指标 | 数值 |
|---|---|
| 服务器成本 | ¥0 |
| 用户数据泄露风险 | 0 |
| 后端代码行数 | 0 |
| 我掌握的用户行为数据 | 0 |
| 需要维护的基础设施 | 0 |
最后一行看起来像缺点,但换个角度:我不需要值班、不需要处理数据合规(GDPR/个保法)、不需要担心数据库被拖。作为一个人的团队,这省下的精力是实打实的。
总结
Local-First 不是银弹,它有明确的代价:放弃社交功能、放弃云同步、放弃数据驱动。但对于位置信息这种高敏感数据,以及「独立开发者精力有限」这个现实约束,它是一个值得认真考虑的架构方向。
如果你也在做类似的工具型应用,不妨想想:你的服务器上真的需要存那些数据吗?
「雁过留痕」是一款 iOS 轨迹记录应用,主打像素地图、纯本地存储、Citywalk 探索,目前在 App Store 可下载。