老谭笔记

解决NSData中包含非法UTF-8编码

我们开发中常会遇上将NSData转换为NSString,或通过NSJSONSerialization解析JSON的场景,一旦NSData中包含非法的UTF-8编码,那么结果将是返回nil,但这样的结果并不符合我们预期,因为可能这其中仅仅只是一个编码错误,我们更希望将错误编码丢弃或替换为错误字符.
在Google上找了一圈,有人也实现了这样的方法,但个人觉得写得不够严谨,容错性也不太好,索性自己写一个吧,严格按照RFC3629的标准.

UTF-8是一种变长的编码,针对不同长度的字节有固定的格式,在RFC3629规范中最多只能四个字节,且对范围有区间有要求,更多相关介绍请跳转维基百科UTF-8词条(跳转地址):

1
2
3
4
1字节 0xxxxxxx
2字节 110xxxxx 10xxxxxx
3字节 1110xxxx 10xxxxxx 10xxxxxx
4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

按照这样的规则写了一个NSData的扩展方法,见代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@implementation NSData (UTF8)
- (NSData *)UTF8Data
{
//保存结果
NSMutableData *resData = [[NSMutableData alloc] initWithCapacity:self.length];
//无效编码替代符号(常见 � □ ?)
NSData *replacement = [@"�" dataUsingEncoding:NSUTF8StringEncoding];
uint64_t index = 0;
const uint8_t *bytes = self.bytes;
while (index < self.length)
{
uint8_t len = 0;
uint8_t header = bytes[index];
//单字节
if ((header&0x80) == 0)
{
len = 1;
}
//2字节(并且不能为C0,C1)
else if ((header&0xE0) == 0xC0)
{
if (header != 0xC0 && header != 0xC1)
{
len = 2;
}
}
//3字节
else if((header&0xF0) == 0xE0)
{
len = 3;
}
//4字节(并且不能为F5,F6,F7)
else if ((header&0xF8) == 0xF0)
{
if (header != 0xF5 && header != 0xF6 && header != 0xF7)
{
len = 4;
}
}
//无法识别
if (len == 0)
{
[resData appendData:replacement];
index++;
continue;
}
//检测有效的数据长度(后面还有多少个10xxxxxx这样的字节)
uint8_t validLen = 1;
while (validLen < len && index+validLen < self.length)
{
if ((bytes[index+validLen] & 0xC0) != 0x80)
break;
validLen++;
}
//有效字节等于编码要求的字节数表示合法,否则不合法
if (validLen == len)
{
[resData appendBytes:bytes+index length:len];
}else
{
[resData appendData:replacement];
}
//移动下标
index += validLen;
}
return resData;
}
@end

在Github上的链接地址:https://github.com/tanhaogg/THCategory