在我的macOS应用程序中,我正在使用UserDefaults dictionaryRepresentation有时我会得到编码未知的字符串有什么建议吗?

问题描述

我正在使用Objective-C应用程序,具体地说,我正在使用以下代码收集NSUserDefaults的字典表示形式:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

NSDictionary *userDefaultsDict = [defaults dictionaryRepresentation];

在枚举结果字典的键和对象时,有时我会发现一种不透明的字符串,您可以在下图中看到它:

enter image description here

所以这似乎是编码问题。

如果我尝试打印字符串描述,则调试器将正确打印:

Printing description of obj:
tsuqsx

但是,如果我尝试将obj写入文件或以其他方式使用它,则会得到如下这样的不可读输出:

enter image description here

我想实现以下目标:

  1. 以某种方式检测字符串是否存在编码问题。

  2. 将字符串转换为UTF8编码,以在程序的其余部分中使用它。

任何帮助将不胜感激。谢谢

编辑:可能会出现非常棘手的解决方案,可帮助解释我正在尝试做的事情。

在尝试了所有可能的基于dataUsingEncoding的解决方案并返回之后,我得出了绝对奇怪的以下解决方案,但我将其发布在这里,希望它可以帮助某人猜测编码以及如何处理不可打印的字符:

- (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],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],1);
        } else {
            usefulCounter += 1;
        }
    
    }
    memset(&dest[length],1);

    NSString *returnValue = [[NSString alloc] initWithUTF8String:dest];
    free(dest);


    return returnValue;
}

这将为我返回一个字符串,可用于构建完整的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 *) $1 = 0x0000600003f6c240 @"\x1at\x1c\x1ds\x1d\x1cu\x16\x1fq\x1d\x1bs\x17\x1ax\x10"

我想实现以下目标:

  1. 以某种方式检测字符串是否存在编码问题。
  2. 将字符串转换为UTF8编码,以便在程序的其余部分中使用它。

尝试了所有可能的基于dataUsingEncoding的解决方案并返回后。

此字符串没有编码问题,并且\x1a\x1c等字符是有效字符。 您可以使用ASCII,UTF-8等调用dataUsingEncoding:,但是所有这些字符仍然是 当下。它们称为control characters(或非印刷字符)。链接的Wikipedia页面解释了这些字符是什么以及如何用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),但只会从开头/结尾删除这些字符, 这不是您想要的。您可以使用一个技巧:

- (NSString *)stringByRemovingControlCharacters {
    NSArray<NSString *> *components = [self componentsSeparatedByCharactersInSet:NSCharacterSet.controlCharacterSet];
    return [components componentsJoinedByString:@""];
}

它将控制字符分割为字符串,然后将这些组件重新连接在一起。不是一种非常有效的方法,但是它可以工作。

ICU转换

另一种方法是使用ICU转换(请参见ICU User Guide)。 有一个stringByApplyingTransform:reverse: 方法(NSString),但它仅接受预定义的常量。文档说:

NSStringTransform类型定义的常量提供了基础ICU转换功能提供的功能的子集。要应用《 ICU用户指南》中定义的没有相应的NSStringTransform常量的ICU转换,请创建NSMutableString的实例,然后调用applyTransform:reverse:range:updatedRange:方法。

让我们更新实现:

- (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相同的字符集。文档:

包含Unicode通用类别Cc和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];
}

在这里,我们过滤掉属于unichar的所有字符(controlCharacterSet)。

其他方式

还有其他方法可以遍历字符-例如-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];

如何处理/显示所有这些控件字符取决于编辑器。这是一个示例-Visual Studio代码。

查看-禁用控制字符:

enter image description here

视图-启用控制字符:

enter image description here

BBEdit显示问号(上下颠倒),但是我敢肯定有一种方法可以 切换控制字符渲染。没有安装BBEdit进行验证。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...