深入理解代替单纯记忆
前面分享了iOS IAP 本地货币展示:从一个需求到搞清楚 priceLocale,NumberFormatter部分比较重要,单独开一篇进行记录
一、一句话总模型
numberStyle决定走哪条格式管道currencyCode决定是什么钱(货币身份)currencySymbol决定符号长什么样(仅.currency管道)internationalCurrencySymbol决定 ISO 管道显示什么标识
二、numberStyle:管道选择器
numberStyle 是最关键的属性,决定底层走哪条 ICU 格式化路径。
.currency
面向用户 UI 的本地化货币格式,输出「货币符号 + 数字」组合。货币符号由 locale 推导(如 en_US → $),currencyCode 可显式覆盖 locale 推导的货币身份,currencySymbol 可进一步覆盖最终显示的符号字符串;符号位置、小数位、分组分隔符均由 locale 决定。
| locale | 输出示例 | 说明 |
|---|---|---|
en_US |
$1,000.00 |
符号在左,点号小数 |
de_DE |
1.000,00 € |
符号在右,逗号小数 |
ja_JP |
¥1,000 |
无小数位(JPY 规范) |
ar_SA |
ر.س. 1,000.00 |
符号在左,阿拉伯数字格式 |
适用场景:面向用户的价格展示(应用内购、充值、价格标签等)。
.currencyISOCode
为什么需要它? .currency 使用的货币符号(如 $)存在天然歧义------$ 同时代表美元、加元、澳元、港元等数十种货币,在跨区域的日志、数据上报或金融报表场景中,仅凭符号无法准确识别货币种类。.currencyISOCode 改用 ISO 4217 标准代码(如 USD、CAD、HKD),全球唯一、无歧义。
输出「ISO 4217 货币代码 + 数字」。代码由 locale 推导(与 .currency 一致),currencyCode 可显式覆盖 locale 推导值,internationalCurrencySymbol 可进一步覆盖最终显示的字符串。
| locale | 输出示例 | 说明 |
|---|---|---|
en_US |
USD 1,000.00 |
代码在左 |
de_DE |
1.000,00 EUR |
代码在右(跟随 locale 习惯) |
ja_JP |
JPY 1,000 |
无小数位 |
适用场景:金融报表、服务端日志、数据上报等需要「无歧义货币标识」的场合。
.currencyPlural
输出「数字 + 货币全称(自然语言)」,遵循目标语言的单复数规则。
| locale + 数值 | 输出示例 | 说明 |
|---|---|---|
en_US, 1.00 |
1.00 US dollar |
单数 |
en_US, 2.00 |
2.00 US dollars |
复数 |
zh_CN, 1.00 |
1.00人民币 |
中文无单复数变化 |
适用场景:需要「读出来」的场景(如语音播报、无障碍文本)。日常 UI 展示几乎不使用。
三、核心属性说明
currencyCode:货币身份(数据源)
决定「这是哪种钱」,是数据源,不是显示配置。
- 确定货币单位(USD / JPY / EUR 等)
- 决定默认
currencySymbol($/¥/€) - 决定货币专属小数位数(JPY=0,KWD=3,USD=2)
- 未显式设置时从
locale自动推导
currencySymbol:符号展示(UI 层 override)
只在
.currency管道生效,属于视觉层覆盖,不影响货币语义。
.currency→ 生效;未设置时由locale自动推导.currencyISOCode→ 无效,被忽略
覆盖示例:
ini
let f = NumberFormatter()
f.numberStyle = .currency
f.locale = Locale(identifier: "en_US")
// 默认:$1,000.00
f.currencySymbol = "US$"
// 覆盖后:US$1,000.00(常用于区分美元与其他 $ 货币)
f.currencySymbol = "💰"
// 覆盖后:💰1,000.00
internationalCurrencySymbol:ISO 管道专用标识
只在
.currencyISOCode管道生效,属于标识层覆盖,不走 symbol 体系。
.currencyISOCode→ 生效;未设置时直接使用currencyCode的值(如USD).currency→ 无效,被忽略
覆盖示例:
ini
let f = NumberFormatter()
f.numberStyle = .currencyISOCode
f.locale = Locale(identifier: "en_US")
f.currencyCode = "USD"
// 默认:USD 1,000.00
f.internationalCurrencySymbol = "美元"
// 覆盖后:美元 1,000.00
四、三条管道关系图
五、验证代码与输出结果
ini
func printTest(_ label: String, _ f: NumberFormatter) {
print("---- (label) ----")
print(f.string(from: 1000) ?? "nil")
}
// 测试 1:默认 currency
let f1 = NumberFormatter()
f1.numberStyle = .currency
f1.locale = Locale(identifier: "en_US")
f1.currencyCode = "USD" // locale 已能推导出 USD,此行与不设置效果相同
printTest("Default currency", f1)
// $1,000.00
// 测试 2:覆盖 currencySymbol
let f2 = NumberFormatter()
f2.numberStyle = .currency
f2.locale = Locale(identifier: "en_US")
f2.currencyCode = "USD" // 同上,此行与不设置效果相同
f2.currencySymbol = "💰"
printTest("Override currencySymbol", f2)
// 💰1,000.00
// 测试 3:ISO style + 覆盖 internationalCurrencySymbol
let f3 = NumberFormatter()
f3.numberStyle = .currencyISOCode
f3.locale = Locale(identifier: "en_US")
f3.currencyCode = "USD"
f3.currencySymbol = "💰" // 被忽略
f3.internationalCurrencySymbol = "US_DOLLAR"
printTest("ISO style + override symbols", f3)
// US_DOLLAR 1,000.00
结论:
- 测试 2 验证
currencySymbol在.currency管道生效 - 测试 3 验证
currencySymbol在.currencyISOCode管道被忽略,internationalCurrencySymbol才生效
六、工程实践结论
多区域内购场景的正确姿势
ini
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = priceLocale // 来自 SKProduct.priceLocale
为什么用 .currency 而不是 .currencyISOCode:
内购价格展示的受众是用户,用户习惯看货币符号($、€、¥),ISO 代码(USD、EUR、JPY)是机器/金融领域的表达方式,放在 UI 上不够直观。.currencyISOCode 适合日志、数据上报等对歧义敏感、不面向用户的场合。
只设置这两个属性,原因:
SKProduct.priceLocale已经将locale和currencyCode对齐(Apple 保证)currencyCode从locale自动推导,无需手动设置- 手动设置
currencyCode但不同步locale,会导致「格式规则与货币符号不匹配」------例如locale = ar_SA但currencyCode = USD,输出既非标准美元也非标准里亚尔 currencySymbol同理,修改后破坏 locale 一致性,不适合多区域场景