如何提供真正的 iPhone 空语言环境
How to give a real iPhone null locale
我有一个 iOS 应用可以像这样获取国家/地区代码
NSLocale *currentLocale = [NSLocale autoupdatingCurrentLocale];
NSString *countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
我知道从技术上讲这可以为空,但我假设(错误地)这只会发生在无关紧要的极端情况下。
但现在我有一个用户 phone 正在报告 null locale,它破坏了我的应用程序的一部分,因此他们无法使用它。在进行错误修复时,我只想了解如何进入和退出此状态,以便他们可以解决该错误。但即使他们将语言和区域设置为 English/USA,他们仍然会在语言和区域屏幕上得到这个奇怪的结果:
并且我的应用程序中的错误仍然由于 null locale/region 而出现。根据维基百科,这是底部的 0xa4“货币符号”:
The currency sign ¤ is a character used to denote an unspecified currency.
看来连iOS都不懂他们的语言环境。真正的 phone 是如何发生这种情况的?您如何解决这个问题?
他们在 iOS 14.7.1 上使用 iPhone 12 mini。
这是一个不幸且罕见的错误,似乎与用户首选项被破坏有关 on-device,并且在没有已知原因的情况下,除了尝试检测之外,您无能为力它并回退到一些默认语言环境(如 en_US
)。
挖掘(来自 Hopper 的详细信息):
+[NSLocale autoupdatingCurrentLocale]
创建了一个 NSAutoLocale
的实例,一个私有 class 的实例,其初始值设定项获取 +[NSLocale currentLocale]
并监听 privately-defined 当前语言环境更改时的通知:
+[NSLocale autoupdatingCurrentLocale]:
00000000000bdfd8 stp x29, x30, [sp, #-0x10]! ; Objective C Implementation defined at 0x24fb80 (class method)
00000000000bdfdc mov x29, sp
00000000000bdfe0 adrp x8, #0x353000 ; &@selector(variantFormatter)
00000000000bdfe4 ldr x0, [x8, #0xf58] ; objc_cls_ref_NSAutoLocale,_OBJC_CLASS_$_NSAutoLocale
00000000000bdfe8 bl imp___stubs__objc_opt_new ; objc_opt_new
00000000000bdfec ldp x29, x30, [sp], #0x10
00000000000bdff0 b imp___stubs__objc_autorelease ; objc_autorelease
int -[NSAutoLocale _init](int arg0) {
r19 = arg0;
r0 = [NSLocale currentLocale];
r0 = [r0 retain];
r8 = 0x35603c;
asm { ldpsw x8, x9, [x8] };
*(r19 + r9) = r0;
pthread_mutex_init(r19 + r8, 0x0);
[[NSNotificationCenter defaultCenter] addObserver:r19 selector:@selector(_update:) name:@"kCFLocaleCurrentLocaleDidChangeNotification-4" object:0x0];
r0 = r19;
return r0;
}
NSAutoLocale
响应所有语言环境方法并将它们转发到底层 NSLocale
实例,因此我们可以查看 +currentLocale
returns
+[NSLocale currentLocale]
只是 returns _CFLocaleCopyCurrent()
的结果(NSLocale
和 CFLocaleRef
是 toll-free 桥接):
void +[NSLocale currentLocale]() {
[_CFLocaleCopyCurrent() autorelease];
return;
}
_CFLocaleCopyCurrent()
returns 从多个地方使用不同参数调用的“Guts”函数的共享实现的内容:
int _CFLocaleCopyCurrent() {
rax = __CFLocaleCopyCurrentGuts(0x0, 0x1, 0x0, 0x0);
return rax;
}
__CFLocaleCopyCurrentGuts
太大而无法内联包含在此处,但是当使用给定参数调用时(并且当它没有缓存时“当前语言环境”实例),它通过从当前用户首选项
中查找 AppleLocale
键的值来创建新的语言环境
- 否则,它还会查找
AppleLanguages
字典并尝试根据其中一个值创建语言环境,但这是分开的
如此有效,“当前语言环境”只是一个语言环境 object,由 prefs 中的“AppleLocale”定义的语言环境标识符构成。查看的具体位置是 kCFPreferencesCurrentHost
处 kCFPreferencesCurrentUser
的 kCFPreferencesAnyApplication
应用程序(即全局首选项)——即 defaults read/write NSGlobalDomain ...
返回的内容(相当于 defaults read/write -g ...
).
这个值你可以自己查看,因为我的机器在美国是设置成英文的,所以看到了
$ defaults read -g AppleLocale
en_US
您可以编写一个检查“AppleLocale”的小工具,向“AppleLocale”写入一个值,如果您愿意,可以重新运行它以检查更改,但这也是可行的 in-process (以一种令人费解的方式)。如果你从 Objective-C 某处 forward-declare _CFLocaleResetCurrent()
(桥接 header 会起作用),你可以直接调用清除当前区域缓存的函数,并通过操作 CFPreferences
直接查看当前语言环境的变化:
import Foundation
func overwriteLocaleIdentifier(_ identifier: String) {
// We use kCFPreferencesCurrentApplication to avoid changing system-wide settings.
CFPreferencesSetAppValue("AppleLocale" as CFString, identifier as CFString, kCFPreferencesCurrentApplication)
_CFLocaleResetCurrent()
}
func withOverwrittenLocaleIdentifier(_ identifier: String, _ action: () -> Void) {
let currentIdentifier = Locale.current.identifier
defer { overwriteLocaleIdentifier(currentIdentifier) }
overwriteLocaleIdentifier(identifier)
action()
}
print(Locale.current.identifier)
withOverwrittenLocaleIdentifier("he_IL") {
print(Locale.current.identifier)
}
print(Locale.current.identifier)
对我来说,这会产生
en_US
he_IL
en_US
无论好坏,此行为意味着无论为“AppleLocale”键找到什么值,都将用作当前区域设置标识符。
如果“AppleLocale”值缺失,您将开始看到一些日志:
2021-09-15 16:47:52.013846-0400 LocaleExample[40427:9913027] [User Defaults] CFPrefsPlistSource<0x107b08420> (Domain: kCFPreferencesAnyApplication, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null), Contents Need Refresh: No): Value for key AppleLocale was (null). Expected en_US (defaults(40182): 2021-09-15 16:44:29 (EDT))
当区域设置标识符丢失(或设置为无意义)时,系统绝对会开始出现异常行为,因为区域设置标识符是直接使用的。
那么,这会在什么时候发生?在典型的 iOS 设备上,答案 应该 是“从不”,但事实并非如此。通过典型的UI,没有办法不小心将语言环境标识符设置为无效的东西,但我在野外看到用户的语言环境设置为""
、"en"
(没有国家代码) ,或者只是丢失了,在手头的设备上,确认是库存,而不是越狱等。真正所需要的只是一个破坏“AppleLocale”值的行为不当过程,你可以结束在这种情况下。
除了回退到已知良好的本地(或像 en_US
这样的默认设置)之外,通常没有真正的资源,如果用户要求,请请求他们通过设置重置他们的区域设置(更改设置,然后改回来)。理想情况下,这将使它们恢复到已知的良好状态。
我有一个 iOS 应用可以像这样获取国家/地区代码
NSLocale *currentLocale = [NSLocale autoupdatingCurrentLocale];
NSString *countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
我知道从技术上讲这可以为空,但我假设(错误地)这只会发生在无关紧要的极端情况下。
但现在我有一个用户 phone 正在报告 null locale,它破坏了我的应用程序的一部分,因此他们无法使用它。在进行错误修复时,我只想了解如何进入和退出此状态,以便他们可以解决该错误。但即使他们将语言和区域设置为 English/USA,他们仍然会在语言和区域屏幕上得到这个奇怪的结果:
并且我的应用程序中的错误仍然由于 null locale/region 而出现。根据维基百科,这是底部的 0xa4“货币符号”:
The currency sign ¤ is a character used to denote an unspecified currency.
看来连iOS都不懂他们的语言环境。真正的 phone 是如何发生这种情况的?您如何解决这个问题?
他们在 iOS 14.7.1 上使用 iPhone 12 mini。
这是一个不幸且罕见的错误,似乎与用户首选项被破坏有关 on-device,并且在没有已知原因的情况下,除了尝试检测之外,您无能为力它并回退到一些默认语言环境(如 en_US
)。
挖掘(来自 Hopper 的详细信息):
+[NSLocale autoupdatingCurrentLocale]
创建了一个NSAutoLocale
的实例,一个私有 class 的实例,其初始值设定项获取+[NSLocale currentLocale]
并监听 privately-defined 当前语言环境更改时的通知:+[NSLocale autoupdatingCurrentLocale]: 00000000000bdfd8 stp x29, x30, [sp, #-0x10]! ; Objective C Implementation defined at 0x24fb80 (class method) 00000000000bdfdc mov x29, sp 00000000000bdfe0 adrp x8, #0x353000 ; &@selector(variantFormatter) 00000000000bdfe4 ldr x0, [x8, #0xf58] ; objc_cls_ref_NSAutoLocale,_OBJC_CLASS_$_NSAutoLocale 00000000000bdfe8 bl imp___stubs__objc_opt_new ; objc_opt_new 00000000000bdfec ldp x29, x30, [sp], #0x10 00000000000bdff0 b imp___stubs__objc_autorelease ; objc_autorelease int -[NSAutoLocale _init](int arg0) { r19 = arg0; r0 = [NSLocale currentLocale]; r0 = [r0 retain]; r8 = 0x35603c; asm { ldpsw x8, x9, [x8] }; *(r19 + r9) = r0; pthread_mutex_init(r19 + r8, 0x0); [[NSNotificationCenter defaultCenter] addObserver:r19 selector:@selector(_update:) name:@"kCFLocaleCurrentLocaleDidChangeNotification-4" object:0x0]; r0 = r19; return r0; }
NSAutoLocale
响应所有语言环境方法并将它们转发到底层NSLocale
实例,因此我们可以查看+currentLocale
returns+[NSLocale currentLocale]
只是 returns_CFLocaleCopyCurrent()
的结果(NSLocale
和CFLocaleRef
是 toll-free 桥接):void +[NSLocale currentLocale]() { [_CFLocaleCopyCurrent() autorelease]; return; }
_CFLocaleCopyCurrent()
returns 从多个地方使用不同参数调用的“Guts”函数的共享实现的内容:int _CFLocaleCopyCurrent() { rax = __CFLocaleCopyCurrentGuts(0x0, 0x1, 0x0, 0x0); return rax; }
中查找__CFLocaleCopyCurrentGuts
太大而无法内联包含在此处,但是当使用给定参数调用时(并且当它没有缓存时“当前语言环境”实例),它通过从当前用户首选项AppleLocale
键的值来创建新的语言环境- 否则,它还会查找
AppleLanguages
字典并尝试根据其中一个值创建语言环境,但这是分开的
- 否则,它还会查找
如此有效,“当前语言环境”只是一个语言环境 object,由 prefs 中的“AppleLocale”定义的语言环境标识符构成。查看的具体位置是 kCFPreferencesCurrentHost
处 kCFPreferencesCurrentUser
的 kCFPreferencesAnyApplication
应用程序(即全局首选项)——即 defaults read/write NSGlobalDomain ...
返回的内容(相当于 defaults read/write -g ...
).
这个值你可以自己查看,因为我的机器在美国是设置成英文的,所以看到了
$ defaults read -g AppleLocale
en_US
您可以编写一个检查“AppleLocale”的小工具,向“AppleLocale”写入一个值,如果您愿意,可以重新运行它以检查更改,但这也是可行的 in-process (以一种令人费解的方式)。如果你从 Objective-C 某处 forward-declare _CFLocaleResetCurrent()
(桥接 header 会起作用),你可以直接调用清除当前区域缓存的函数,并通过操作 CFPreferences
直接查看当前语言环境的变化:
import Foundation
func overwriteLocaleIdentifier(_ identifier: String) {
// We use kCFPreferencesCurrentApplication to avoid changing system-wide settings.
CFPreferencesSetAppValue("AppleLocale" as CFString, identifier as CFString, kCFPreferencesCurrentApplication)
_CFLocaleResetCurrent()
}
func withOverwrittenLocaleIdentifier(_ identifier: String, _ action: () -> Void) {
let currentIdentifier = Locale.current.identifier
defer { overwriteLocaleIdentifier(currentIdentifier) }
overwriteLocaleIdentifier(identifier)
action()
}
print(Locale.current.identifier)
withOverwrittenLocaleIdentifier("he_IL") {
print(Locale.current.identifier)
}
print(Locale.current.identifier)
对我来说,这会产生
en_US
he_IL
en_US
无论好坏,此行为意味着无论为“AppleLocale”键找到什么值,都将用作当前区域设置标识符。
如果“AppleLocale”值缺失,您将开始看到一些日志:
2021-09-15 16:47:52.013846-0400 LocaleExample[40427:9913027] [User Defaults] CFPrefsPlistSource<0x107b08420> (Domain: kCFPreferencesAnyApplication, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null), Contents Need Refresh: No): Value for key AppleLocale was (null). Expected en_US (defaults(40182): 2021-09-15 16:44:29 (EDT))
当区域设置标识符丢失(或设置为无意义)时,系统绝对会开始出现异常行为,因为区域设置标识符是直接使用的。
那么,这会在什么时候发生?在典型的 iOS 设备上,答案 应该 是“从不”,但事实并非如此。通过典型的UI,没有办法不小心将语言环境标识符设置为无效的东西,但我在野外看到用户的语言环境设置为""
、"en"
(没有国家代码) ,或者只是丢失了,在手头的设备上,确认是库存,而不是越狱等。真正所需要的只是一个破坏“AppleLocale”值的行为不当过程,你可以结束在这种情况下。
除了回退到已知良好的本地(或像 en_US
这样的默认设置)之外,通常没有真正的资源,如果用户要求,请请求他们通过设置重置他们的区域设置(更改设置,然后改回来)。理想情况下,这将使它们恢复到已知的良好状态。