Подтвердить что ты не робот

NSString initWithData возвращает значение null

Я извлекаю данные с веб-сайта через NSURLConnection и удаляя полученные данные в экземпляре NSMutableData. В методе делегата connectionDidFinishLoading данные преобразуются в строку с вызовом соответствующего метода NSString:

NSString *result = [[NSString alloc] initWithData:data 
                                     encoding:NSUTF8StringEncoding]

Полученная строка оказывается пустой. Однако, если я использую NSASCIIStringEncoding, я получаю соответствующую строку, хотя символы unicode искажаются, как ожидалось. Заголовок сервера Content-Type не указывает кодировку UTF-8, но я попытался создать несколько разных сайтов с похожим сценарием, и преобразование строк происходит просто отлично. Похоже, что проблема относится только к данному веб-сервису, но я не знаю, почему.

На стороне примечания, тянет веб-страницы и данные из хорошей практики API, то есть буферизации данных, преобразования в строку и последующего управления строкой?

Очень ценно!

4b9b3361

Ответ 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 страниц уменьшены до одной строки хе... если, конечно, ошибочно ошибочно.