我有一个iOS应用程序,可以得到这样的国家代码
NSLocale *currentLocale = [NSLocale autoupdatingCurrentLocale];
NSString *countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
我理解技术上这可以是空的,但我(错误地)假设这只能发生在不重要的角落情况下。
但现在我有一个用户,他的手机报告为空locale,它破坏了我的应用程序的一部分,所以他们不能使用它。在进行bug修复时,我只是想了解如何进入和离开这种状态,以便他们能够围绕bug工作。但即使他们将自己的语言和地区设置为英语/美国,他们仍然会在语言和地区屏幕上得到这个奇怪的结果:
在我的应用程序中,由于null locale/region的错误仍然发生。根据维基百科,这是底部的0xa4“货币符号”:
货币符号¤是一种不指定货币的符号。
所以即使是iOS也不理解他们的语言环境。这在真正的手机上是如何发生的?你又该如何解决它?
他们使用的是iOS 14。7。1上的iPhone 12 mini。
###这是一个不幸的、有点罕见的bug,似乎与用户偏好在设备上被破坏有关,没有一个已知的原因,你不能做什么,除非尝试检测它,并返回到一些默认的locale(如en_US
).
深入挖掘(来自Hopper的细节):
& lt; Foundation>+[NSLocale autoupdatingCurrentLocale]创建一个NSAutoLocale的实例,一个私有类的实例,它的初始化器获取+[NSLocale currentLocale]并侦听一个私有定义的通知,当当前区域设置改变时:
+[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返回什么
& lt; CoreFoundation>+[NSLocale currentLocale]简单地返回_CFLocaleCopyCurrent() (NSLocale和CFLocaleRef是免费桥接的)的结果:
void +[NSLocale currentLocale]() {
[_CFLocaleCopyCurrent() autorelease];
return;
}
_CFLocaleCopyCurrent()返回一个“Guts”函数的共享实现的内容,该函数从多个地方用不同的参数调用:
int _CFLocaleCopyCurrent() {
rax = __CFLocaleCopyCurrentGuts(0x0, 0x1, 0x0, 0x0);
return rax;
}
& lt; CoreFoundation>__CFLocaleCopyCurrentGuts太大了,不能在这里包含内联,但是当使用给定的参数调用(并且当它没有缓存的“当前区域设置”实例)时,它会通过从当前用户首选项查找AppleLocale键的值来创建一个新的区域设置
如果没有,它也会AppleLanguages
dictionary and tries to create a 字典,并试图从其中一个值创建语言环境,但这是独立的
因此,“当前区域设置”实际上只是一个区域设置对象,由prefs中“AppleLocale”定义的区域标识符构造。具体的位置是kCFPreferencesAnyApplication应用程序(即全局prefs)的kCFPreferencesCurrentUser在kCFPreferencesCurrentHost - i。e。什么是返回的默认读/写NSGlobalDomain…(相当于默认的read/write -g…)
您可以自己检查这个值,因为我的机器在美国被设置为英语
$ defaults read -g AppleLocale
en_US
您可以编写一个小工具来检查“AppleLocale”,将一个值写入“AppleLocale”并重新运行它来检查更改,如果您愿意的话,但这也可以在进程内进行(以一种令人费解的方式)。如果你在Objective-C的某个地方提前声明_CFLocaleResetCurrent()(桥接头会起作用),你可以直接调用清除当前区域缓存的函数,并通过操作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,没有办法意外地设置locale标识符为无效的东西,但我看到野生用户的locale设置为“”“en”(没有国家代码),或只是在设备上确认为股票,而不是越狱等。它真正需要的是一个错误的过程来打击“AppleLocale”值,你可以在这种情况下结束。
通常情况下,除了退回到一个已知的好的本地语言环境(或像en_US这样的默认设置),如果用户请求通过Settings(更改设置,然后再更改回设置)来重置locale之外,没有任何实际的办法。理想情况下,这将使他们回到一个已知的良好状态。