原载于herkuang.info
在做iOS开发中一个很常见的应用场景就是从服务器接收一段数据然后把它显示出来。但是有时候服务器在数据处理时,比如拼接之类的操作,会出一些问题,造成传过来的数据并不符合指定的编码。(我碰到过的一种情况是,一段宣称用GB18030编码的文字中突然出现了几个用UTF8编码的词语)。浏览器在处理这个问题时,一般就会出现乱码,常用的编程语言在处理这个问题时,也是以乱码显示,而ObjC的NSString则直接返回了一个nil。这一直让我比较头疼,在旧版股吧的开发中曾经碰到因为接口返回的数据里面有一个字节是错的,导致整个接口返回的数据不能用了,当时在国内几个网站上问了别人,得到的都是一些不靠谱的回答。
今天同事又遇到了这个问题,不过他找到了如何让UTF8编码的里面混有错误字节的数据,以容错的方式显示出来而不是nil(见这个Gist),其实原理很简单,一个一个字节读过来,参照UTF8编码的说明,判断是不是合法字节,如果不是,用“?”来代替。根据这段代码,我改了一个用于GB18030编码的混有不合法字节的数据的容错转换。
NSData+HG_DataHealing.h
#import <Foundation/Foundation.h>
@interface NSData (HG_DataHealing)
- (NSString *)GB18030String;
@end
NSData+HG_DataHealing.m
#import "NSData+HG_DataHealing.h"
@implementation NSData (HG_DataHealing)
- (NSString *)GB18030String {
NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
NSString *str = [[NSString alloc]initWithData:self encoding:enc];
if (!str) {
str = [[NSString alloc]initWithData:[self dataByHealingGB18030Stream] encoding:enc];
}
return str;
}
- (NSData *)dataByHealingGB18030Stream {
NSUInteger length = [self length];
if (length == 0) {
return self;
}
static NSString * replacementCharacter = @"?";
NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
NSData *replacementCharacterData = [replacementCharacter dataUsingEncoding:enc];
NSMutableData *resultData = [NSMutableData dataWithCapacity:self.length];
const Byte *bytes = [self bytes];
static const NSUInteger bufferMaxSize = 1024;
Byte buffer[bufferMaxSize];
NSUInteger bufferIndex = 0;
NSUInteger byteIndex = 0;
BOOL invalidByte = NO;
#define FlushBuffer() if (bufferIndex > 0) { \
[resultData appendBytes:buffer length:bufferIndex]; \
bufferIndex = 0; \
}
#define CheckBuffer() if ((bufferIndex+5) >= bufferMaxSize) { \
[resultData appendBytes:buffer length:bufferIndex]; \
bufferIndex = 0; \
}
while (byteIndex < length) {
Byte byte = bytes[byteIndex];
//检查第一位
if (byte >= 0 && byte <= (Byte)0x7f) {
//单字节文字
CheckBuffer();
buffer[bufferIndex++] = byte;
} else if (byte >= (Byte)0x81 && byte <= (Byte)0xfe){
//可能是双字节,可能是四字节
if (byteIndex + 1 >= length) {
//这是最后一个字节了,但是这个字节表明后面应该还有1或3个字节,那么这个字节一定是错误字节
FlushBuffer();
return resultData;
}
Byte byte2 = bytes[++byteIndex];
if (byte2 >= (Byte)0x40 && byte <= (Byte)0xfe && byte != (Byte)0x7f) {
//是双字节,并且可能合法
Byte tuple[] = {byte, byte2};
CFStringRef cfstr = CFStringCreateWithBytes(kCFAllocatorDefault, tuple, 2, kCFStringEncodingGB_18030_2000, false);
if (cfstr) {
CFRelease(cfstr);
CheckBuffer();
buffer[bufferIndex++] = byte;
buffer[bufferIndex++] = byte2;
} else {
//这个双字节字符不合法,但byte2可能是下一字符的第一字节
byteIndex -= 1;
invalidByte = YES;
}
} else if (byte2 >= (Byte)0x30 && byte2 <= (Byte)0x39) {
//可能是四字节
if (byteIndex + 2 >= length) {
FlushBuffer();
return resultData;
}
Byte byte3 = bytes[++byteIndex];
if (byte3 >= (Byte)0x81 && byte3 <= (Byte)0xfe) {
// 第三位合法,判断第四位
Byte byte4 = bytes[++byteIndex];
if (byte4 >= (Byte)0x30 && byte4 <= (Byte)0x39) {
//第四位可能合法
Byte tuple[] = {byte, byte2, byte3, byte4};
CFStringRef cfstr = CFStringCreateWithBytes(kCFAllocatorDefault, tuple, 4, kCFStringEncodingGB_18030_2000, false);
if (cfstr) {
CFRelease(cfstr);
CheckBuffer();
buffer[bufferIndex++] = byte;
buffer[bufferIndex++] = byte2;
buffer[bufferIndex++] = byte3;
buffer[bufferIndex++] = byte4;
} else {
//这个四字节字符不合法,但是byte2可能是下一个合法字符的第一字节,回退3位
//并且将byte1,byte2用?替代
byteIndex -= 3;
invalidByte = YES;
}
} else {
//第四字节不合法
byteIndex -= 3;
invalidByte = YES;
}
} else {
// 第三字节不合法
byteIndex -= 2;
invalidByte = YES;
}
} else {
// 第二字节不是合法的第二位,但可能是下一个合法的第一位,所以回退一个byte
invalidByte = YES;
byteIndex -= 1;
}
if (invalidByte) {
invalidByte = NO;
FlushBuffer();
[resultData appendData:replacementCharacterData];
}
}
byteIndex++;
}
FlushBuffer();
return resultData;
}
@end
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。