NSString initWithData возвращает значение null
Я извлекаю данные с веб-сайта через NSURLConnection
и удаляя полученные данные в экземпляре NSMutableData
. В методе делегата connectionDidFinishLoading
данные преобразуются в строку с вызовом соответствующего метода NSString:
NSString *result = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding]
Полученная строка оказывается пустой. Однако, если я использую NSASCIIStringEncoding
, я получаю соответствующую строку, хотя символы unicode искажаются, как ожидалось. Заголовок сервера Content-Type
не указывает кодировку UTF-8, но я попытался создать несколько разных сайтов с похожим сценарием, и преобразование строк происходит просто отлично. Похоже, что проблема относится только к данному веб-сервису, но я не знаю, почему.
На стороне примечания, тянет веб-страницы и данные из хорошей практики API, то есть буферизации данных, преобразования в строку и последующего управления строкой?
Очень ценно!
Ответы
Ответ 1
Вы говорите, что это "определенно UTF-8", но без заголовка Content-Type вы действительно этого не знаете. (И даже если у вас есть заголовок, говорящий, что он все равно может быть неправильным.)
Я предполагаю, что ваши данные обычно ASCII, которые всегда правильно анализируются как UTF-8, но вы иногда пытаетесь проанализировать данные, которые фактически закодированы в ISO 8859-1 или кодовой странице Windows 1252. Такие данные обычно будут в основном ASCII, но с некоторыми байтами вне диапазона 0-127 ASCII. UTF-8 ожидал бы, что такие байты сформируют последовательность блоков кода в пределах определенной последовательности диапазонов, но в других кодировках любой байт, независимо от значения, является полным символом сам по себе. Попытка интерпретировать не-ASCII-данные, отличные от UTF-8, как UTF-8, почти всегда приведет вас к неправильным результатам (неправильные символы) или вообще не приведет к результатам (невозможно декодировать; декодер возвращает nil
), поскольку данные никогда не закодированы в UTF-8 в первую очередь.
Сначала вы должны попробовать UTF-8, и если это не удается, используйте ISO 8859-1. Если вы разрешаете пользователю получать любую веб-страницу, вы должны позволить им изменить кодировку, которую вы используете для декодирования данных, в случае, если они обнаружат, что это действительно 8859-9 или код-1252 или еще одна 8-разрядная кодировка.
Если вы загружаете данные с определенного сервера, и особенно если у вас есть влияние на то, что выполняется на этом сервере, вы должны заставить его обслуживать точный заголовок Content-Type и/или исправить любую ошибку, вызывающую ее подавать текст, который не находится в UTF-8.
Ответ 2
Как сказал Питер, заголовок типа контента - это всего лишь "намек" на то, что ожидается отправленное содержимое. На стороне сервера вы можете установить любой тип содержимого и отправить любые байтовые последовательности, которые могут быть недействительными.
У меня была точно такая же проблема с неправильными данными UTF-8, которая включала символы ISO-8859-1 (Latin-1) (французские акценты).
Википедия о UTF-8 стоит прочитать, чтобы понять эту проблему и как обрабатывать ошибки кодирования.
Дело в том, что NSString initWithData:encoding:
строгая реализация просто возвращает nil, когда возникает ошибка декодирования. (в отличие от java, например, использующего заменяющий символ)
Решение peter преобразования большинства данных UTF-8 в латинский-1 не удовлетворяло меня.
(Все символы UTF-8 становятся некорректными, только для одного латинского 1 неустойчивого символа)
Лучший вариант - это исправление на стороне сервера, конечно, но я не несу ответственности на этой стороне...
Итак, я посмотрел глубже и нашел решение, используя библиотеку GNU libiconv C (доступную на OSX и iOS)
Принцип использования iconv для удаления недействительных символов UTF-8 (т.е. "Prété" станет "prt" )
Вот пример кода, эквивалентный командной строке iconv -c -f UTF-8 -t UTF-8 invalid.txt > cleaned.txt
#include "iconv.h"
- (NSData *)cleanUTF8:(NSData *)data {
iconv_t cd = iconv_open("UTF-8", "UTF-8"); // convert to UTF-8 from UTF-8
int one = 1;
iconvctl(cd, ICONV_SET_DISCARD_ILSEQ, &one); // discard invalid characters
size_t inbytesleft, outbytesleft;
inbytesleft = outbytesleft = data.length;
char *inbuf = (char *)data.bytes;
char *outbuf = malloc(sizeof(char) * data.length);
char *outptr = outbuf;
if (iconv(cd, &inbuf, &inbytesleft, &outptr, &outbytesleft)
== (size_t)-1) {
NSLog(@"this should not happen, seriously");
return nil;
}
NSData *result = [NSData dataWithBytes:outbuf length:data.length - outbytesleft];
iconv_close(cd);
free(outbuf);
return result;
}
Затем полученный NSData
можно безопасно декодировать, используя NSUTF8StringEncoding
Обратите внимание, что последние iconv также позволяют использовать резервные методы, используя:
iconvctl(cd, ICONV_SET_FALLBACKS, &fallbacks);
Используя резервную ошибку в Unicode-ошибках, вы можете использовать заменяющий символ или, лучше, попробовать другую кодировку.
В моем случае мне удалось отступить к LATIN-1, где UTF-8 потерпел неудачу, что привело к 99% -ным положительным конверсиям. Посмотрите исходный код iconv, чтобы понять его.
Ответ 3
Кодировка по умолчанию для HTTP, если ни один не указан, - ISO-8859-1. Если HTTP-ответ соответствует HTTP/1.1, и он не указывает кодировку набора символов, то есть кодировку, которую он использует.
Попробуйте декодировать строку с помощью этого NSISOLatin1StringEncoding.
Ответ 4
Данные могли быть в другой кодировке unicode, такой как UTF16 или в некоторых совершенно разных кодировках.
Есть библиотеки, которые могут угадать кодировку, используемую в данных, но это должно быть последним средством.
Если вы используете веб-службу, у этой веб-службы должна быть документация, в которой указывается, какую кодировку она использует. Найдите его или попросите поставщика веб-службы, которая его кодирует. Если ни один из них не доступен, вы должны попытаться получить образцы данных и определить кодировку для этого и использовать их в программе.
На стороне примечания, тянет веб-страницы и данные из хорошей практики API, то есть буферизации данных, преобразования в строку и последующего управления строкой?
Это зависит от размера данных. Если бы это было мало, это было бы прекрасно. Если бы это было важно, было бы лучше разобраться с данными по частям.
Ответ 5
Подождите минуту, OP читает из сети в первую очередь? почему бы не использовать NSString
stringWithContentsOfURL:usedEncoding:error:
Возвращает строку, созданную путем чтения данных с заданного URL-адреса и возвращает по ссылке кодировку, используемую для интерпретации данных.
+ (id)stringWithContentsOfURL:(NSURL *)url usedEncoding:(NSStringEncoding *)enc error:(NSError **)error
страницы n страниц уменьшены до одной строки хе... если, конечно, ошибочно ошибочно.