在我的 macOS 应用程序中,我使用 UserDefaults dictionaryRepresentation。有时我会得到编码未知的字符串。有什么建议吗?
In my macOS application, I am working with UserDefaults dictionaryRepresentation. Sometimes I get strings with unknown encoding. Any suggesition?
我正在使用 Objective-C 应用程序,具体来说,我正在使用以下代码收集 NSUserDefaults 的字典表示形式:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *userDefaultsDict = [defaults dictionaryRepresentation];
在枚举生成的字典的键和对象时,有时我会发现一种不透明的字符串,您可以在下图中看到:
看来是编码问题。
如果我尝试打印字符串的描述,调试器会正确打印:
Printing description of obj:
tsuqsx
但是,如果我尝试将 obj 写入文件,或以任何其他方式使用它,我会得到如下不可读的输出:
我想实现的是:
通过某种方式检测到字符串存在编码问题
将字符串转换为 UTF8 编码以在程序的其余部分使用它。
非常感谢任何帮助。谢谢
编辑:非常 Hacky 可能的解决方案,有助于解释我正在尝试做的事情。
在尝试了基于 dataUsingEncoding 的所有可能解决方案并返回之后,我最终得到了以下解决方案,这绝对很奇怪,但我 post 在这里,希望它可以帮助别人猜测编码和如何处理不可打印的字符:
- (BOOL)isProblematicString:(NSString *)candidateString {
BOOL returnValue = YES;
if ([candidateString length] <= 2) {
return NO;
}
const char *temp = [candidateString UTF8String];
long length = temp[0];
char *dest = malloc(length + 1);
long ctr = 1;
long usefulCounter = 0;
for (ctr = 1;ctr <= length;ctr++) {
if ((ctr - 1) % 3 == 0) {
memcpy(&dest[ctr - usefulCounter - 1],&temp[ctr],1);
} else {
if (ctr != 1 && ctr < [candidateString length]) {
if (temp[ctr] < 0x10 || temp[ctr] > 0x1F) {
returnValue = NO;
}
}
usefulCounter += 1;
}
}
memset(&dest[length],0,1);
free(dest);
return returnValue;
}
- (NSString *)utf8StringFromUnknownEncodedString:(NSString*)originalUnknownString {
const char *temp = [originalUnknownString UTF8String];
long length = temp[0];
char *dest = malloc(length + 1);
long ctr = 1;
long usefulCounter = 0;
for (ctr = 1;ctr <= length;ctr++) {
if ((ctr - 1) % 3 == 0) {
memcpy(&dest[ctr - usefulCounter - 1],&temp[ctr],1);
} else {
usefulCounter += 1;
}
}
memset(&dest[length],0,1);
NSString *returnValue = [[NSString alloc] initWithUTF8String:dest];
free(dest);
return returnValue;
}
这 returns 我可以用来构建完整的 UTF8 字符串的字符串。我正在寻找一个干净的解决方案。任何帮助是极大的赞赏。谢谢
我们正在谈论来自 /Library/Preferences/.GlobalPreferences.plist
的字符串
(键com.apple.preferences.timezone.new.selected_city
)。
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:@"com.apple.preferences.timezone.new.selected_city"];
NSLog(@"%@", city); // \^Zt\^\^]s\^]\^\u\^V\^_q\^]\^[s\^W\^Zx\^P
(lldb) p [city description]
(__NSCFString *) = 0x0000600003f6c240 @"\x1at\x1c\x1ds\x1d\x1cu\x16\x1fq\x1d\x1bs\x17\x1ax\x10"
What I would like to achieve is the following:
- Detect in some way that the string has the encoding problem.
- Convert the string to UTF8 encoding to use it in the rest of the program.
&
After trying all possible solutions based on dataUsingEncoding and back.
此字符串没有编码问题,\x1a
、\x1c
、...等字符是有效字符。
您可以使用 ASCII、UTF-8 等调用 dataUsingEncoding:
,但所有这些字符仍将是
当前的。它们被称为 control characters(或 non-printing 个字符)。链接的维基百科页面解释了这些字符是什么以及它们是如何在 ASCII、扩展 ASCII 和 unicode 中定义的。
您正在寻找的是如何从字符串中删除控制字符的方法。
删除控制字符
我们可以为我们的新方法创建一个类别:
@interface NSString (ControlCharacters)
- (NSString *)stringByRemovingControlCharacters;
@end
@implementation NSString (ControlCharacters)
- (NSString *)stringByRemovingControlCharacters {
// TODO Remove control characters
return self;
}
@end
在下面的所有示例中,city
变量都是以这种方式创建的...
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:@"com.apple.preferences.timezone.new.selected_city"];
... 并包含 @"\x1at\x1c\x1ds\x1d\x1cu\x16\x1fq\x1d\x1bs\x17\x1ax\x10"
。还有所有
下面的示例使用以下代码进行了测试:
NSString *cityWithoutCC = [city stringByRemovingControlCharacters];
// tsuqsx
NSLog(@"%@", cityWithoutCC);
// {length = 6, bytes = 0x747375717378}
NSLog(@"%@", [cityWithoutCC dataUsingEncoding:NSUTF8StringEncoding]);
拆分与合并
一种方法是利用 NSCharacterSet.controlCharacterSet
。
有一个stringByTrimmingCharactersInSet:
方法 (NSString
),但它仅从 beginning/end 中删除这些字符,
这不是你要找的。您可以使用一个技巧:
- (NSString *)stringByRemovingControlCharacters {
NSArray<NSString *> *components = [self componentsSeparatedByCharactersInSet:NSCharacterSet.controlCharacterSet];
return [components componentsJoinedByString:@""];
}
它按控制字符拆分字符串,然后将这些组件连接回去。不是一个非常有效的方法,但它有效。
ICU 变换
另一种方法是使用 ICU 变换(参见 ICU User Guide)。
有一个 stringByApplyingTransform:reverse:
方法 (NSString
),但它只接受预定义的常量。文档说:
The constants defined by the NSStringTransform
type offer a subset of the functionality provided by the underlying ICU transform functionality. To apply an ICU transform defined in the ICU User Guide that doesn't have a corresponding NSStringTransform
constant, create an instance of NSMutableString
and call the applyTransform:reverse:range:updatedRange:
method instead.
让我们更新我们的实现:
- (NSString *)stringByRemovingControlCharacters {
NSMutableString *result = [self mutableCopy];
[result applyTransform:@"[[:Cc:] [:Cf:]] Remove"
reverse:NO
range:NSMakeRange(0, self.length)
updatedRange:nil];
return result;
}
[:Cc:]
表示控制字符,[:Cf:]
表示格式字符。两者都代表与已经提到的 NSCharacterSet.controlCharacterSet
相同的字符集。文档:
A character set containing the characters in Unicode General Category Cc and Cf.
迭代字符
NSCharacterSet
还提供了 characterIsMember:
方法。这里我们需要遍历字符 (unichar
) 并检查它是否是控制字符。
让我们更新我们的实现:
- (NSString *)stringByRemovingControlCharacters {
if (self.length == 0) {
return self;
}
NSUInteger length = self.length;
unichar characters[length];
[self getCharacters:characters];
NSUInteger resultLength = 0;
unichar result[length];
NSCharacterSet *controlCharacterSet = NSCharacterSet.controlCharacterSet;
for (NSUInteger i = 0 ; i < length ; i++) {
if ([controlCharacterSet characterIsMember:characters[i]] == NO) {
result[resultLength++] = characters[i];
}
}
return [NSString stringWithCharacters:result length:resultLength];
}
这里我们过滤掉属于controlCharacterSet
.
的所有字符(unichar
)
其他方式
还有其他方法可以遍历字符 - 例如 - Most efficient way to iterate over all the chars in an NSString.
BBEdit 和其他人
让我们将此字符串写入文件:
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:@"com.apple.preferences.timezone.new.selected_city"];
[city writeToFile:@"/Users/zrzka/city.txt"
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
所有这些控制字符是如何由编辑器决定的handled/displayed。这是一个示例 - Visual Studio 代码。
视图 - 渲染控制字符关闭:
视图 - 渲染控制字符:
BBEdit 显示问号(上下颠倒),但我确定有办法
切换控制字符渲染。没有安装 BBEdit 来验证它。
我正在使用 Objective-C 应用程序,具体来说,我正在使用以下代码收集 NSUserDefaults 的字典表示形式:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *userDefaultsDict = [defaults dictionaryRepresentation];
在枚举生成的字典的键和对象时,有时我会发现一种不透明的字符串,您可以在下图中看到:
看来是编码问题。
如果我尝试打印字符串的描述,调试器会正确打印:
Printing description of obj:
tsuqsx
但是,如果我尝试将 obj 写入文件,或以任何其他方式使用它,我会得到如下不可读的输出:
我想实现的是:
通过某种方式检测到字符串存在编码问题
将字符串转换为 UTF8 编码以在程序的其余部分使用它。
非常感谢任何帮助。谢谢
编辑:非常 Hacky 可能的解决方案,有助于解释我正在尝试做的事情。
在尝试了基于 dataUsingEncoding 的所有可能解决方案并返回之后,我最终得到了以下解决方案,这绝对很奇怪,但我 post 在这里,希望它可以帮助别人猜测编码和如何处理不可打印的字符:
- (BOOL)isProblematicString:(NSString *)candidateString {
BOOL returnValue = YES;
if ([candidateString length] <= 2) {
return NO;
}
const char *temp = [candidateString UTF8String];
long length = temp[0];
char *dest = malloc(length + 1);
long ctr = 1;
long usefulCounter = 0;
for (ctr = 1;ctr <= length;ctr++) {
if ((ctr - 1) % 3 == 0) {
memcpy(&dest[ctr - usefulCounter - 1],&temp[ctr],1);
} else {
if (ctr != 1 && ctr < [candidateString length]) {
if (temp[ctr] < 0x10 || temp[ctr] > 0x1F) {
returnValue = NO;
}
}
usefulCounter += 1;
}
}
memset(&dest[length],0,1);
free(dest);
return returnValue;
}
- (NSString *)utf8StringFromUnknownEncodedString:(NSString*)originalUnknownString {
const char *temp = [originalUnknownString UTF8String];
long length = temp[0];
char *dest = malloc(length + 1);
long ctr = 1;
long usefulCounter = 0;
for (ctr = 1;ctr <= length;ctr++) {
if ((ctr - 1) % 3 == 0) {
memcpy(&dest[ctr - usefulCounter - 1],&temp[ctr],1);
} else {
usefulCounter += 1;
}
}
memset(&dest[length],0,1);
NSString *returnValue = [[NSString alloc] initWithUTF8String:dest];
free(dest);
return returnValue;
}
这 returns 我可以用来构建完整的 UTF8 字符串的字符串。我正在寻找一个干净的解决方案。任何帮助是极大的赞赏。谢谢
我们正在谈论来自 /Library/Preferences/.GlobalPreferences.plist
的字符串
(键com.apple.preferences.timezone.new.selected_city
)。
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:@"com.apple.preferences.timezone.new.selected_city"];
NSLog(@"%@", city); // \^Zt\^\^]s\^]\^\u\^V\^_q\^]\^[s\^W\^Zx\^P
(lldb) p [city description]
(__NSCFString *) = 0x0000600003f6c240 @"\x1at\x1c\x1ds\x1d\x1cu\x16\x1fq\x1d\x1bs\x17\x1ax\x10"
What I would like to achieve is the following:
- Detect in some way that the string has the encoding problem.
- Convert the string to UTF8 encoding to use it in the rest of the program.
&
After trying all possible solutions based on dataUsingEncoding and back.
此字符串没有编码问题,\x1a
、\x1c
、...等字符是有效字符。
您可以使用 ASCII、UTF-8 等调用 dataUsingEncoding:
,但所有这些字符仍将是
当前的。它们被称为 control characters(或 non-printing 个字符)。链接的维基百科页面解释了这些字符是什么以及它们是如何在 ASCII、扩展 ASCII 和 unicode 中定义的。
您正在寻找的是如何从字符串中删除控制字符的方法。
删除控制字符
我们可以为我们的新方法创建一个类别:
@interface NSString (ControlCharacters)
- (NSString *)stringByRemovingControlCharacters;
@end
@implementation NSString (ControlCharacters)
- (NSString *)stringByRemovingControlCharacters {
// TODO Remove control characters
return self;
}
@end
在下面的所有示例中,city
变量都是以这种方式创建的...
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:@"com.apple.preferences.timezone.new.selected_city"];
... 并包含 @"\x1at\x1c\x1ds\x1d\x1cu\x16\x1fq\x1d\x1bs\x17\x1ax\x10"
。还有所有
下面的示例使用以下代码进行了测试:
NSString *cityWithoutCC = [city stringByRemovingControlCharacters];
// tsuqsx
NSLog(@"%@", cityWithoutCC);
// {length = 6, bytes = 0x747375717378}
NSLog(@"%@", [cityWithoutCC dataUsingEncoding:NSUTF8StringEncoding]);
拆分与合并
一种方法是利用 NSCharacterSet.controlCharacterSet
。
有一个stringByTrimmingCharactersInSet:
方法 (NSString
),但它仅从 beginning/end 中删除这些字符,
这不是你要找的。您可以使用一个技巧:
- (NSString *)stringByRemovingControlCharacters {
NSArray<NSString *> *components = [self componentsSeparatedByCharactersInSet:NSCharacterSet.controlCharacterSet];
return [components componentsJoinedByString:@""];
}
它按控制字符拆分字符串,然后将这些组件连接回去。不是一个非常有效的方法,但它有效。
ICU 变换
另一种方法是使用 ICU 变换(参见 ICU User Guide)。
有一个 stringByApplyingTransform:reverse:
方法 (NSString
),但它只接受预定义的常量。文档说:
The constants defined by the
NSStringTransform
type offer a subset of the functionality provided by the underlying ICU transform functionality. To apply an ICU transform defined in the ICU User Guide that doesn't have a correspondingNSStringTransform
constant, create an instance ofNSMutableString
and call theapplyTransform:reverse:range:updatedRange:
method instead.
让我们更新我们的实现:
- (NSString *)stringByRemovingControlCharacters {
NSMutableString *result = [self mutableCopy];
[result applyTransform:@"[[:Cc:] [:Cf:]] Remove"
reverse:NO
range:NSMakeRange(0, self.length)
updatedRange:nil];
return result;
}
[:Cc:]
表示控制字符,[:Cf:]
表示格式字符。两者都代表与已经提到的 NSCharacterSet.controlCharacterSet
相同的字符集。文档:
A character set containing the characters in Unicode General Category Cc and Cf.
迭代字符
NSCharacterSet
还提供了 characterIsMember:
方法。这里我们需要遍历字符 (unichar
) 并检查它是否是控制字符。
让我们更新我们的实现:
- (NSString *)stringByRemovingControlCharacters {
if (self.length == 0) {
return self;
}
NSUInteger length = self.length;
unichar characters[length];
[self getCharacters:characters];
NSUInteger resultLength = 0;
unichar result[length];
NSCharacterSet *controlCharacterSet = NSCharacterSet.controlCharacterSet;
for (NSUInteger i = 0 ; i < length ; i++) {
if ([controlCharacterSet characterIsMember:characters[i]] == NO) {
result[resultLength++] = characters[i];
}
}
return [NSString stringWithCharacters:result length:resultLength];
}
这里我们过滤掉属于controlCharacterSet
.
unichar
)
其他方式
还有其他方法可以遍历字符 - 例如 - Most efficient way to iterate over all the chars in an NSString.
BBEdit 和其他人
让我们将此字符串写入文件:
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:@"com.apple.preferences.timezone.new.selected_city"];
[city writeToFile:@"/Users/zrzka/city.txt"
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
所有这些控制字符是如何由编辑器决定的handled/displayed。这是一个示例 - Visual Studio 代码。
视图 - 渲染控制字符关闭:
视图 - 渲染控制字符:
BBEdit 显示问号(上下颠倒),但我确定有办法 切换控制字符渲染。没有安装 BBEdit 来验证它。