Formatters:让数据对用户更友好
Formatters: Make data human-friendly
2020年6月25日
一句话判断
Apple 在 iOS 14 重新设计了 Formatter 体系——如果你还在用 DateFormatter 那套老旧 API,你不仅写了很多冗余代码,还在浪费宝贵的启动时间。
这场 Session 讲了什么
Formatter 是 Foundation 框架中负责将机器数据(日期、数字、度量值等)转换为人类可读文本的核心组件。iOS 14 引入了全新的 Formatter 体系,彻底重构了 DateFormatter、NumberFormatter 等老 API 的使用方式。新的 API 采用”配置式”设计——你创建一个 Formatter 实例,设置格式风格,然后直接格式化值,不再需要反复修改同一个实例的属性。
Session 的核心亮点是 Date.Formatter 和 Number.Formatter 的新便捷 API。你不再需要创建和管理 Formatter 实例,而是直接使用静态方法。比如 date.formatted(date: .long, time: .short) 就能得到一个符合用户区域设置的长日期+短时间字符串。这种方式不仅代码更简洁,性能也大幅提升——Apple 声称新 API 比 DateFormatter 快了 80 倍。
另一个重要的新增是 ListFormatter 和 RelativeDateTimeFormatter 的增强。ListFormatter 可以根据区域设置正确地格式化列表(中文用顿号、英文用牛津逗号),而 RelativeDateTimeFormatter 可以输出”3小时前""昨天”这样的相对时间描述。这些细节决定了你的应用在不同语言环境下是否显得”地道”。
值得深挖的点
新 Formatter 的性能优势
传统的 DateFormatter 是线程不安全的重量级对象,创建和配置的代价高昂。很多开发者会在项目中维护全局的 Formatter 单例来避免重复创建。新 API 在底层使用了缓存的 locale 数据,每次格式化操作的开销极小。Apple 的基准测试显示,在循环中格式化 10 万个日期,新 API 比 DateFormatter 快 80 倍。这意味着你再也不需要维护 Formatter 缓存池了。
Formatter 与 SwiftUI 的集成
SwiftUI 中可以直接使用 .formatted() 方法在各种 Text 视图中应用格式化。这不仅简化了代码,还确保了格式化逻辑与 UI 的自动刷新同步。当用户的区域设置改变时(比如切换语言),SwiftUI 会自动重新格式化并刷新界面。
代码片段
import Foundation
// 新的日期格式化 API,告别 DateFormatter
let now = Date()
// 各种预设风格
now.formatted() // "2020/6/25 14:30"
now.formatted(date: .long, time: .short) // "2020年6月25日 14:30"
now.formatted(date: .complete, time: .complete) // "2020年6月25日星期四 14:30:00 CST"
now.formatted(.dateTime.year().month().day()) // "2020年6月25日"
// 相对时间格式化
let threeHoursAgo = Date().addingTimeInterval(-3 * 3600)
threeHoursAgo.formatted(.relative(presentation: .named)) // "3小时前"
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
yesterday.formatted(.relative(presentation: .named)) // "昨天"
// 数字和度量值格式化
import Foundation
// 数字格式化
let price = 128.5
price.formatted(.currency(code: "CNY")) // "¥128.50"
price.formatted(.currency(code: "USD")) // "$128.50"
price.formatted(.number.precision(.fractionLength(2))) // "128.50"
// 百分比格式化
let ratio = 0.856
ratio.formatted(.percent) // "86%"
// 大数格式化(自动添加单位)
let population = 1400000000
population.formatted(.number.notation(.compactName)) // "14亿"
// 度量值格式化
let distance = Measurement(value: 5.2, unit: UnitLength.kilometers)
distance.formatted() // "5.2 km"
distance.formatted(.measurement(width: .wide, usage: .road)) // "5.2公里"
// 列表格式化和复合格式化
import Foundation
// ListFormatter:根据区域自动选择分隔符
let fruits = ["苹果", "香蕉", "橙子"]
fruits.formatted() // 中文: "苹果、香蕉和橙子"
// 英文环境: "apples, bananas, and oranges"(牛津逗号)
// 日期范围格式化
let startDate = Date()
let endDate = Calendar.current.date(byAdding: .day, value: 3, to: startDate)!
(startDate...endDate).formatted() // "6月25日至6月28日"
// 自定义格式化配置
let customFormatter = Date.FormatStyle()
.year(.defaultDigits)
.month(.abbreviated)
.day(.twoDigits)
.hour(.defaultDigits(amPM: .abbreviated))
.minute(.twoDigits)
.locale(Locale(identifier: "zh_CN"))
let formattedDate = now.formatted(customFormatter)
// "20年6月25日 下午2:30"
最佳实践
- 不要再缓存
DateFormatter实例——新的.formatted()API 已经内置了高效缓存 - 始终使用 Formatter API 处理面向用户的文本,不要手动拼接日期和数字字符串
- 使用
ListFormatter来格式化枚举列表,它能自动处理不同语言的列表分隔规则 - 测试时切换到阿拉伯语、希伯来语等 RTL 语言,确保 Formatter 正确处理文本方向
- 对于性能敏感的场景(如列表中格式化大量数据),可以预先创建一个
FormatStyle实例复用
还有什么值得关注
PersonNameComponents.Formatter可以根据上下文智能选择显示全名还是缩写形式ISO8601DateFormatter仍然适用于 API 通信场景,新 API 更适合 UI 展示MeasurementFormatter支持自动单位转换——用户设置英制时,公里自动显示为英里