iOS - 如何将带有错误编码的NSData转为NSString

原载于herkuang.info
在作iOS开发中一个很常见的应用场景就是从服务器接收一段数据而后把它显示出来。可是有时候服务器在数据处理时,好比拼接之类的操做,会出一些问题,形成传过来的数据并不符合指定的编码。(我碰到过的一种状况是,一段宣称用GB18030编码的文字中忽然出现了几个用UTF8编码的词语)。浏览器在处理这个问题时,通常就会出现乱码,经常使用的编程语言在处理这个问题时,也是以乱码显示,而ObjC的NSString则直接返回了一个nil。这一直让我比较头疼,在旧版股吧的开发中曾经碰到由于接口返回的数据里面有一个字节是错的,致使整个接口返回的数据不能用了,当时在国内几个网站上问了别人,获得的都是一些不靠谱的回答。git

今天同事又遇到了这个问题,不过他找到了如何让UTF8编码的里面混有错误字节的数据,以容错的方式显示出来而不是nil(见这个Gist),其实原理很简单,一个一个字节读过来,参照UTF8编码的说明,判断是否是合法字节,若是不是,用“?”来代替。根据这段代码,我改了一个用于GB18030编码的混有不合法字节的数据的容错转换。github

Gist地址编程

NSData+HG_DataHealing.hsegmentfault

#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