先说这个 App 是什么
上个月上线了一个叫 NMTI 人格测试的 iOS App,跟市面上那堆严肃学术风的 MBTI 工具刻意拉开距离。
题目全是职场场景,结果不是「INTJ:建筑师」这种,而是职场版的「六边形悍匪」「优雅吸血鬼」「金牌老黄牛」之类。配上一段毒舌文案,比如 VAMP 的:「全公司最忙的人是给你背锅的那个,全公司最闲的是你,但你把这叫战略性授权。」
写完我自己看着都觉得有点扎心。
目前刚上线,下载量还在爬,但开发过程里有几个技术决策值得记一下,尤其是数据模型设计和「测朋友」这个双向互测功能。
数据结构设计:一开始没想到要支持多套题包
最初设计的时候,我只有「职场版」一套题,16 种人格的文案全写死在 Personality struct 里。但做到一半意识到,如果要出「学生版」题包,文案语境完全不同——「六边形悍匪」在职场语境是「推进机器」,在校园语境更偏「学习系统」那个方向。
直接改 Personality struct 不现实,字段全混在一起会很乱。所以我拆了一层 PersonalityCopy,专门存按场景区分的文案:
struct PersonalityCopy {
let slogan: String
let tagline: String
let fullDesc: String
}
extension Personality {
func copy(for pack: QuizPack) -> PersonalityCopy {
switch pack {
case .workplace:
return PersonalityCopy(slogan: slogan, tagline: tagline, fullDesc: fullDesc)
case .student:
return Self.studentCopy[id]
?? PersonalityCopy(slogan: slogan, tagline: tagline, fullDesc: fullDesc)
}
}
}
.student 分支拿不到对应文案时,用职场版兜底而不是抛错——原因很现实:新题包上线前,16 种人格的文案不一定同时写完,用职场版先顶着比直接崩溃或显示空白好得多。这是一个「内容生产进度跟不上代码发布节奏」时的防御策略。
还有一个旧存档兼容的坑:老版本的 ArchiveEntry 没有 pack 字段,直接 decode 会崩。在自定义 init(from:) 里用 decodeIfPresent 处理,拿不到就默认职场版:
pack = try c.decodeIfPresent(QuizPack.self, forKey: .pack) ?? .workplace
这个细节如果漏了,老用户升级后历史记录全部崩掉。
「测朋友」功能的思路
这个功能的核心价值不是技术,是社交动机。内测阶段我发现,拿到带链接的「去测测朋友」入口之后,几乎所有人都会主动把链接发出去——而单纯截图分享的用户里,大部分发完就结束了,对方不会主动回来找入口。链接把「我想知道你是什么类型」这个动机变成了一个可执行的操作,截图做不到这点。
逻辑是:A 把测试链接发给 B,B 完成测试后,A 看到的不只是自己的结果,还有系统给两人生成的「相处建议」——类似「你是悍匪,对方是老黄牛,你擅长定方向,对方擅长兜底,建议你少甩锅多给反馈」这种。
16 种人格里每种都配了 rivalCode 和 partnerCode,分别标记「最容易起冲突的类型」和「最搭的类型」。双向互测结果出来后,系统根据两人的 personalityCode 组合查关系表,生成对应文案。
关系表的存储我试了三个方案:二维 Dictionary 在 Swift 里 key 类型约束麻烦,要么用元组要么用自定义 Hashable,写起来比想象中啰嗦;枚举 switch 写到一半发现 16×16 = 256 个 case 实在不现实,有 130 多条非对称组合,全手写一遍我会疯。最后回到最简单的:每个 Personality 实例只存一个 rivalCode 和一个 partnerCode,关系文案按两者 id 拼接成 key 来查字典。结构最平,查起来也直接。
海报生成:没用第三方库
结果页有个「生成分享海报」的功能,输出一张带人格标签和 slogan 的图片。
用的是 SwiftUI 的 ImageRenderer,iOS 16+ 支持,把一个 View 直接渲染成 UIImage。代码量很少,但 ImageRenderer 必须在主线程跑,而且对字体渲染和阴影的处理跟真实 UI 展示有细微差异。
具体表现是:海报 View 里的 shadow 修饰符渲染出来偏重,跟设计稿对不上。最后把所有 shadow 改成手动叠一层半透明 View 来模拟,才对齐。这个差异在模拟器上几乎看不出来,真机导出才明显,调参花了不少时间。
关于内容本身
这个 App 的开发时间里,写代码大概占了 40%,写文案占了 60%。
16 种人格 × 两套题包,每种人格有 slogan、tagline、fullDesc 三层文案,加上互测关系文案,总字数大概 3 万字左右。每条文案的目标是:读完之后觉得「这说的是我」,同时又想截图发给朋友说「这说的是你」。
这两个感受要同时成立挺难的,前者需要精准,后者需要普适。我的解法是:精准描述一个具体行为场景,而不是描述性格特质。「你从不加班,是因为总有人比你更焦虑更愿意接活」比「你善于借助外力」有力得多。
现在的状态和一个没解决的问题
刚上线一周,技术上最想打磨的是「测朋友」的链接跳转体验——目前用 Universal Link,但在微信里打开时会先弹一个「即将离开微信」的确认页,这一步有明显流失,我目前没找到规避的方法。
理论上 Universal Link 应该直接跳转,但微信对外链做了拦截层,这个确认页是微信自己加的,不在我的控制范围内。有没有做过类似场景的同学,这步的流失你们是怎么处理的?