原載於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