Highlight
Apple Retail 用 App Groups、Swift packages、XCTest、Instruments 和配置驱动 UI 管理多款店内员工 App,让共享数据、共享组件、测试优先级和地区门店配置都能在同一套工程实践下落地。
核心内容
Apple Retail 有 500 多家门店。员工每天使用多款内部 App,处理预约、产品目录、门店布局、客户沟通和运营状态。如果每个 App 都独立处理认证、图片缓存、扫描、模型对象和 UI 组件,工程团队会把大量时间花在重复实现和一致性修复上。
这场 session 给出的做法是把“套件”当成一个工程系统设计。多个 App 仍然保持聚焦,但共享能力被放进明确的边界里:App Groups 负责跨 App 数据,Swift packages 负责复用代码,测试按优先级进入不同频率的执行队列,生产环境差异由配置文件和服务端开关驱动。
这套方法的重点不是某个单一框架。它把企业 App 的四个长期问题放在一起处理:多 App 之间怎样共享,公共代码怎样沉淀,发布前怎样验证,发布后怎样适应不同地区、市场和门店。对企业团队来说,这比做一个庞大的单体 App 更容易维护,也更适合多个业务团队并行演进。
演讲中的扫描示例也很有代表性。Apple Retail 把 Vision 和 AVFoundation 包装成内部的 RetailScanner package,向业务 App 暴露统一的配置对象、默认 UI 和 delegate 回调。业务工程师不需要重复理解相机、条码、OCR 和辅助功能细节,就能在不同 App 里得到一致的扫描体验。
最后,生产配置把业务变化从客户端发版中拆出来。客户表单里的 Email 或 Phone Number 字段要下线时,团队修改托管在服务器上的配置文件,App 下次启动后读取新配置,UI 就能更新。这个机制减少了临时发版,但也要求客户端、服务端和配置版本保持兼容。
详细内容
App Groups:先解决跨 App 共享数据
(01:23)Apple Retail 最早采用的 iOS 能力之一是 App Groups。开启后,同一开发者门户下的多个 App 可以通过共享容器交换 UserDefaults 和文件。Xcode 的入口在 Signing and Capabilities 中,添加 App Groups capability 后再创建 group。
(01:57)演讲给出的代码从共享 UserDefaults 开始:
let sharedDefaults = UserDefaults(suiteName: "group.com.apple.myappgroup")!
sharedDefaults.set("My Cool Value", forKey: "MyKeyName")
let myKeyNameValue = sharedDefaults.string(forKey: "MyKeyName")
关键点:
suiteName使用 App Group identifier,只有加入同一 group 的 App 才能访问这份 defaults。set(_:forKey:)写入共享键值,适合放轻量配置和状态。string(forKey:)读取同一个共享域里的值,其他同组 App 也能按相同 key 取得。- transcript 还提到 shared files,适合比键值更大的共享数据。
Swift packages:把重复能力收敛成稳定边界
(02:23)Swift packages 在这场演讲里的作用是减少重复代码、降低人为错误,并让工程师跨团队移动时更容易理解已有代码。Apple Retail 还建立了专门维护 packages 的团队,让这些组件有长期负责人。
(02:57)演讲列出的候选能力很具体:基于 URLSession 的认证包、图片抓取与磁盘缓存包、UIKit 的自定义 table view cell 和产品详情视图、客户/预约/产品等模型对象,以及扫描包。这些能力有一个共同点:多个 App 都要用,且行为需要一致。
(05:03)扫描包的条码示例展示了统一配置对象和 view controller 初始化模式。这里的 RetailScanner 是 Apple Retail 的内部 package,用来说明包装方式,不是公开系统框架。
import RetailScanner
let scanOptions = BarcodeScanOptions()
scanOptions.scanRegion = .regular
scanOptions.shouldAddSupplementaryView = false
scanOptions.shouldShowBarcodeDetector = true
let barcodeViewController = BarcodeScannerViewController(scanOptions: scanOptions)
barcodeViewController.delegate = self
关键点:
BarcodeScanOptions集中保存扫描区域、辅助视图和探测器显示设置。BarcodeScannerViewController(scanOptions:)把配置作为初始化参数,方便在多个 App 里复用同一套配置。delegate接收扫描成功和失败消息,业务 App 只处理结果,不重复实现相机和识别流程。
(05:23)OCR 示例沿用同样的配置和初始化模式:
import RetailScanner
let scanOptions = OCRScanOptions(
scanRegion: .custom(CGSize(width: 400, height: 100)),
accessibilityBehavior: .vibrate,
shouldAddSupplementaryView: true,
validation: nil,
shouldShowResultView: true
)
scanOptions.recognitionLevel = .fast
let ocrViewController = OCRScannerViewController(
scanOptions: scanOptions
)
ocrViewController.delegate = self
关键点:
scanRegion把 OCR 识别范围限定到自定义尺寸,减少无关画面干扰。accessibilityBehavior让扫描反馈能照顾辅助功能需求,演讲里提到扫描包支持自定义 accessibility support。recognitionLevel = .fast对应实时 OCR 场景,优先保证扫描过程中的反馈速度。- 条码和 OCR 使用相同设计模式,业务工程师切换能力时不需要重新学习一套 API。
测试:把人工回归拆成可运行的工程流程
(05:48)App 做完之后,Apple Retail 没有依赖大量人工测试。演讲把测试分成 unit testing、UI testing 和 performance testing。单元测试由工程团队在写类和函数时补齐,目标是覆盖各个 code path 的期望返回值。
(06:40)UI testing 使用 XCUITest,把 QE 团队已有的手工测试用例编码为 Swift。测试创建后还要按重要程度拆分,因为 UI 测试运行时间长,最重要的测试需要更高频执行。
check-in: run the most important UI tests
daily: run a broader set of regression tests
weekly: run lower-priority or longer tests
关键点:
check-in级别测试负责尽快拦截高风险回归。daily测试扩大覆盖面,适合放核心路径之外的场景。weekly测试承载耗时更长、优先级较低的回归内容。- 按优先级调度能缩短关键失败的发现时间。
(07:21)性能测试关注两件事:rendering performance 和 battery performance。门店设备整天在店面使用,员工把设备拿去充电会损失生产力,所以电量表现和滚动、拖拽等交互流畅度一样重要。
Instruments
→ rendering performance
→ memory usage
→ battery usage
→ long-running operations
关键点:
Instruments是 Xcode 内置工具,演讲中用于分析执行时间、内存和电量异常。rendering performance对应列表滚动、拖拽等高频交互。battery usage对企业门店场景很关键,因为设备离开店面就会影响员工工作。- 这些指标要在真实设备上观察,才能贴近员工全天使用的状态。
配置驱动 UI:让生产差异脱离客户端发版
(08:41)生产管理部分讲了两类配置:client based configuration file 和 server based configuration。前者是客户端内有映射,但配置文件托管在服务器上;后者是从服务端下载的布尔值,用来驱动 App 行为。
(09:10)客户端配置适合控制 UI 元素。Apple Retail 可以按 geo、market 和 store level 调整体验,不需要改代码或重新提交客户端 build。
PLIST attributes in client code
→ hosted as JSON on the server
→ app downloads attributes during startup
→ UI renders only the configured fields
关键点:
PLIST attributes是客户端已知的字段定义。hosted as JSON表示配置内容从服务器分发。startup阶段下载配置,保证 App 打开时使用最新字段。- UI 按配置渲染,业务团队可以快速调整门店或市场级差异。
(12:10)客户表单示例展示了配置驱动 UI 的落点。官方代码片段只展示了两类 cell 的配置入口:
func configuredCellForLabel(for customerInfoField: CustomerInfoField, at indexPath: IndexPath) -> UITableViewCell { ... }
func configuredCellForPhoneNumber(for customerInfoField: CustomerInfoField, at indexPath: IndexPath) -> UITableViewCell { ... }
关键点:
CustomerInfoField把表单字段抽象成通用输入,便于由配置决定显示哪些字段。configuredCellForLabel处理普通文本字段,例如 First Name、Last Name 或 Email。configuredCellForPhoneNumber处理 Phone Number 这类需要特殊输入行为的字段。- 当配置文件删除 Email 或 Phone Number,App 下次启动后不再渲染对应 cell。
(13:42)配置驱动带来的代价是兼容性管理。新的 server 配置可能旧客户端不认识,新的客户端属性也可能还没有服务端映射。Apple Retail 用 config versioning 判断下载到的 client/server 配置版本,再决定展示哪种体验;遇到双方无法互相支持的情况,就要求用户更新 App。
核心启发
-
做什么:把认证和网络访问收进共享 package。为什么值得做:transcript 明确提到 Apple Retail 把认证建在
URLSession之上,业务工程师不用反复处理认证细节。怎么开始:先抽出 request 构造、token 刷新和错误处理,再让各业务 App 依赖同一个 Swift package。 -
做什么:为条码和 OCR 建一个统一扫描入口。为什么值得做:扫描能力需要 Vision、AVFoundation、默认 UI、辅助功能反馈和 delegate 回调,重复实现容易出现体验差异。怎么开始:照着演讲的
BarcodeScanOptions、OCRScanOptions和 view controller 初始化模式,先把最常用的扫描流程包成 package。 -
做什么:把企业 App 的 UI 测试按优先级分层运行。为什么值得做:演讲中的 check-in、daily、weekly 节奏能让关键失败更早暴露,同时保留完整回归覆盖。怎么开始:把现有手工用例转成 XCUITest,再给每条用例标记优先级和运行频率。
-
做什么:把门店或地区差异放进配置驱动表单。为什么值得做:客户表单示例说明字段下线可以通过配置完成,不需要新的客户端 build。怎么开始:先把表单字段抽象成
CustomerInfoField,再让启动流程下载托管在服务器上的 JSON 配置。 -
做什么:给配置系统加入版本兼容和强制更新规则。为什么值得做:session 明确指出 server 与 client 配置可能互不支持,缺少版本判断会让旧客户端拿到无法理解的新配置。怎么开始:为 client config 和 server config 分别记录版本,启动时先检查兼容矩阵,再决定启用新体验或提示更新。
关联 Session
- Create custom apps for employees — 讲如何从员工痛点设计企业 App,是本 session 的产品侧前置内容。
- Leverage enterprise identity and authentication — 讲企业身份、SSO extension 和 Managed Apple ID,可补齐 App 套件里的登录与凭据管理。
- Custom app distribution with Apple Business Manager — 讲自定义企业 App 的创建和分发流程,接在套件开发完成之后。
- Distribute binary frameworks as Swift packages — 讲 Swift packages 分发二进制框架,补充共享组件的包化交付方式。
- Get your test results faster — 讲 Xcode test plans、xcodebuild 和 XCTest 超时机制,延伸本 session 的测试扩展性话题。
评论
GitHub Issues · utterances