Лучший способ сериализации NSData в hexadeximal string

Я ищу метод nice-cocoa для сериализации объекта NSData в шестнадцатеричную строку. Идея состоит в сериализации устройстваToken, используемого для уведомления, перед отправкой его на мой сервер.

У меня есть следующая реализация, но я думаю, что должен быть более короткий и лучший способ сделать это.

+ (NSString*) serializeDeviceToken:(NSData*) deviceToken { NSMutableString *str = [NSMutableString stringWithCapacity:64]; int length = [deviceToken length]; char *bytes = malloc(sizeof(char) * length); [deviceToken getBytes:bytes length:length]; for (int i = 0; i < length; i++) { [str appendFormat:@"%02.2hhX", bytes[i]]; } free(bytes); return str; } 

Это категория, применяемая к NSData, которую я написал. Он возвращает шестнадцатеричную NSString, представляющую NSData, где данные могут быть любой длины. Возвращает пустую строку, если NSData пуст.

NSData + Conversion.h

 #import  @interface NSData (NSData_Conversion) #pragma mark - String Conversion - (NSString *)hexadecimalString; @end 

NSData + Conversion.m

 #import "NSData+Conversion.h" @implementation NSData (NSData_Conversion) #pragma mark - String Conversion - (NSString *)hexadecimalString { /* Returns hexadecimal string of NSData. Empty string if data is empty. */ const unsigned char *dataBuffer = (const unsigned char *)[self bytes]; if (!dataBuffer) return [NSString string]; NSUInteger dataLength = [self length]; NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)]; for (int i = 0; i < dataLength; ++i) [hexString appendString:[NSString stringWithFormat:@"%02lx", (unsigned long)dataBuffer[i]]]; return [NSString stringWithString:hexString]; } @end 

Применение:

 NSData *someData = ...; NSString *someDataHexadecimalString = [someData hexadecimalString]; 

Это «вероятно» лучше, чем вызов [someData description] а затем удаление пробелов, <и s> и>. Снимающие персонажи просто чувствуют себя слишком «взломанными». Кроме того, вы никогда не знаете, изменит ли Apple форматирование описания NSData в будущем.

ПРИМЕЧАНИЕ. У меня были люди, которые обращаются ко мне с просьбой о лицензировании кода в этом ответе. Настоящим я посвящаю свое авторское право в коде, который я опубликовал в этом ответе, в общественном достоянии.

Вот высоко оптимизированный метод категорий NSData для генерации шестнадцатеричной строки. В то время как ответ @Dave Gallagher достаточно для относительно небольшого размера, память и производительность процессора ухудшаются для больших объемов данных. Я профилировал это с файлом 2 МБ на моем iPhone 5. Сравнение времени было 0,05 против 12 секунд. Объем памяти при этом методе пренебрежимо мал, в то время как другой метод увеличил кучу до 70 МБ!

 - (NSString *) hexString { NSUInteger bytesCount = self.length; if (bytesCount) { const char *hexChars = "0123456789ABCDEF"; const unsigned char *dataBuffer = self.bytes; char *chars = malloc(sizeof(char) * (bytesCount * 2 + 1)); if (chars == NULL) { // malloc returns null if attempting to allocate more memory than the system can provide. Thanks Cœur [NSException raise:@"NSInternalInconsistencyException" format:@"Failed to allocate more memory" arguments:nil]; return nil; } char *s = chars; for (unsigned i = 0; i < bytesCount; ++i) { *s++ = hexChars[((*dataBuffer & 0xF0) >> 4)]; *s++ = hexChars[(*dataBuffer & 0x0F)]; dataBuffer++; } *s = '\0'; NSString *hexString = [NSString stringWithUTF8String:chars]; free(chars); return hexString; } return @""; } 

Использование свойства описания NSData не должно считаться приемлемым механизмом для HEX, кодирующего строку. Это свойство предназначено только для описания и может быть изменено в любое время. В качестве примечания, pre-iOS, свойство описания NSData даже не возвращало данные в шестнадцатеричной форме.

Извините за то, что вы участвуете в решении, но важно, чтобы энергия стала сериализовать его без поддержки копий API, предназначенного для чего-то другого, кроме сериализации данных.

 @implementation NSData (Hex) - (NSString*)hexString { NSUInteger length = self.length; unichar* hexChars = (unichar*)malloc(sizeof(unichar) * (length*2)); unsigned char* bytes = (unsigned char*)self.bytes; for (NSUInteger i = 0; i < length; i++) { unichar c = bytes[i] / 16; if (c < 10) { c += '0'; } else { c += 'A' - 10; } hexChars[i*2] = c; c = bytes[i] % 16; if (c < 10) { c += '0'; } else { c += 'A' - 10; } hexChars[i*2+1] = c; } NSString* retVal = [[NSString alloc] initWithCharactersNoCopy:hexChars length:length*2 freeWhenDone:YES]; return [retVal autorelease]; } @end 

Вот более быстрый способ сделать преобразование:

BenchMark (среднее время для преобразования данных на 1024 байта повторяется 100 раз):

Дейв Галлахер: ~ 8,070 мс
NSProgrammer: ~ 0,077 мс
Питер: ~ 0.031 мс
Это одно: ~ 0,017 мс

 @implementation NSData (BytesExtras) static char _NSData_BytesConversionString_[512] = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; -(NSString*)bytesString { UInt16* mapping = (UInt16*)_NSData_BytesConversionString_; register UInt16 len = self.length; char* hexChars = (char*)malloc( sizeof(char) * (len*2) ); // --- Coeur's contribution - a safe way to check the allocation if (hexChars == NULL) { // we directly raise an exception instead of using NSAssert to make sure assertion is not disabled as this is irrecoverable [NSException raise:@"NSInternalInconsistencyException" format:@"failed malloc" arguments:nil]; return nil; } // --- register UInt16* dst = ((UInt16*)hexChars) + len-1; register unsigned char* src = (unsigned char*)self.bytes + len-1; while (len--) *dst-- = mapping[*src--]; NSString* retVal = [[NSString alloc] initWithBytesNoCopy:hexChars length:self.length*2 encoding:NSASCIIStringEncoding freeWhenDone:YES]; #if (!__has_feature(objc_arc)) return [retVal autorelease]; #else return retVal; #endif } @end 

Функциональная версия Swift

Один лайнер:

 let hexString = UnsafeBufferPointer(start: UnsafePointer(data.bytes), count: data.length).map { String(format: "%02x", $0) }.joinWithSeparator("") 

Вот в форме многократного использования и самостоятельного документирования:

 extension NSData { func base16EncodedString(uppercase uppercase: Bool = false) -> String { let buffer = UnsafeBufferPointer(start: UnsafePointer(self.bytes), count: self.length) let hexFormat = uppercase ? "X" : "x" let formatString = "%02\(hexFormat)" let bytesAsHexStrings = buffer.map { String(format: formatString, $0) } return bytesAsHexStrings.joinWithSeparator("") } } 

В качестве альтернативы, используйте reduce("", combine: +) вместо joinWithSeparator("") которое будет рассматриваться как функциональный мастер ваших сверстников.


Изменить: я изменил String ($ 0, radix: 16) на String (формат: «% 02x», $ 0), потому что для одного номера цифр необходимо иметь нулевой пробел

Ответ Питера перенесен на Свифт

 func hexString(data:NSData)->String{ if data.length > 0 { let hexChars = Array("0123456789abcdef".utf8) as [UInt8]; let buf = UnsafeBufferPointer(start: UnsafePointer(data.bytes), count: data.length); var output = [UInt8](count: data.length*2 + 1, repeatedValue: 0); var ix:Int = 0; for b in buf { let hi = Int((b & 0xf0) >> 4); let low = Int(b & 0x0f); output[ix++] = hexChars[ hi]; output[ix++] = hexChars[low]; } let result = String.fromCString(UnsafePointer(output))!; return result; } return ""; } 

swift3

 func hexString()->String{ if count > 0 { let hexChars = Array("0123456789abcdef".utf8) as [UInt8]; return withUnsafeBytes({ (bytes:UnsafePointer) -> String in let buf = UnsafeBufferPointer(start: bytes, count: self.count); var output = [UInt8](repeating: 0, count: self.count*2 + 1); var ix:Int = 0; for b in buf { let hi = Int((b & 0xf0) >> 4); let low = Int(b & 0x0f); output[ix] = hexChars[ hi]; ix += 1; output[ix] = hexChars[low]; ix += 1; } return String(cString: UnsafePointer(output)); }) } return ""; } 

Мне нужно было решить эту проблему и найти ответы здесь очень полезны, но я беспокоюсь о производительности. Большинство из этих ответов связаны с копированием данных из NSData, поэтому я написал следующее для преобразования с низкими накладными расходами:

 @interface NSData (HexString) @end @implementation NSData (HexString) - (NSString *)hexString { NSMutableString *string = [NSMutableString stringWithCapacity:self.length * 3]; [self enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop){ for (NSUInteger offset = 0; offset < byteRange.length; ++offset) { uint8_t byte = ((const uint8_t *)bytes)[offset]; if (string.length == 0) [string appendFormat:@"%02X", byte]; else [string appendFormat:@" %02X", byte]; } }]; return string; } 

Это предварительно выделяет пространство в строке для всего результата и позволяет избежать копирования содержимого NSData с помощью enumerateByteRangesUsingBlock. Изменение X в x в строке формата будет использовать строчные шестнадцатеричные цифры. Если вам не нужен разделитель между байтами, вы можете уменьшить оператор

 if (string.length == 0) [string appendFormat:@"%02X", byte]; else [string appendFormat:@" %02X", byte]; 

до

 [string appendFormat:@"%02X", byte]; 

Мне нужен был ответ, который будет работать для строк переменной длины, вот что я сделал:

 + (NSString *)stringWithHexFromData:(NSData *)data { NSString *result = [[data description] stringByReplacingOccurrencesOfString:@" " withString:@""]; result = [result substringWithRange:NSMakeRange(1, [result length] - 2)]; return result; } 

Отлично работает как расширение classа NSString.

Вы всегда можете использовать [yourString uppercaseString] для заглавной буквы в описании данных

Лучший способ сериализации / десериализации NSData в NSString – использовать кодировщик / декодер Google Toolbox для Mac Base64. Просто перетащите в свой проект приложения файлы GTMBase64.m, GTMBase64.he GTMDefines.h из пакета Foundation и сделайте что-нибудь вроде

 /** * Serialize NSData to Base64 encoded NSString */ -(void) serialize:(NSData*)data { self.encodedData = [GTMBase64 stringByEncodingData:data]; } /** * Deserialize Base64 NSString to NSData */ -(NSData*) deserialize { return [GTMBase64 decodeString:self.encodedData]; } 

Вот решение, использующее Swift 3

 extension Data { public var hexadecimalString : String { var str = "" enumerateBytes { buffer, index, stop in for byte in buffer { str.append(String(format:"%02x",byte)) } } return str } } extension NSData { public var hexadecimalString : String { return (self as Data).hexadecimalString } } 
 @implementation NSData (Extn) - (NSString *)description { NSMutableString *str = [[NSMutableString alloc] init]; const char *bytes = self.bytes; for (int i = 0; i < [self length]; i++) { [str appendFormat:@"%02hhX ", bytes[i]]; } return [str autorelease]; } @end Now you can call NSLog(@"hex value: %@", data) 

Измените %08x на %08X чтобы получить символы капитала.

Swift + Свойство.

Я предпочитаю иметь шестнадцатеричное представление как свойство (то же, что и bytes и свойства description ):

 extension NSData { var hexString: String { let buffer = UnsafeBufferPointer(start: UnsafePointer(self.bytes), count: self.length) return buffer.map { String(format: "%02x", $0) }.joinWithSeparator("") } var heXString: String { let buffer = UnsafeBufferPointer(start: UnsafePointer(self.bytes), count: self.length) return buffer.map { String(format: "%02X", $0) }.joinWithSeparator("") } } 

Идея заимствована из этого ответа

 [deviceToken description] 

Вам нужно будет удалить пробелы.

Лично я base64 кодировал deviceToken , но это вопрос вкуса.

  • Определите addAction для уведомлений Android
  • Как нарисовать представление поверх всего?
  • Создание пользовательского уведомления, андроида
  • Как использовать Notification.Builder
  • Push-уведомления, когда приложение закрыто
  • Истекает ли идентификатор регистрации GCM?
  • Уведомления Android с кнопками на нем
  • Android - создайте уведомление, TaskStackBuilder.addParentStack не работает
  • я хочу показать уведомление в 8:00 ежедневно
  • PendingIntent работает правильно для первого уведомления, но неправильно для остальных
  • Отсутствие права на уведомление Push Push
  • Interesting Posts

    Получение контекста приложения

    Ошибка grub2: вне диска

    Изменить и продолжить: «Изменения не допускаются, когда …»

    Как мы можем обойти пустой заголовок в PagerTitleStrip и PagerTabStrip?

    Написание надежного R-кода: пространства имен, маскирование и использование оператора `::`

    Пользовательский пул streamов в параллельном streamе Java 8

    Объем (струнных) литералов

    Какое свободное программное обеспечение можно использовать для сканирования сканированных изображений

    Как получить позицию, выбранную в RecyclerView?

    Как вы определяете формат даты, используемый при маршалах JAXB xsd: dateTime?

    В чем разница между IQueryable и IEnumerable ?

    Может ли Unity быть сделано, чтобы не вызывать синхронное исключение Exception все время?

    Может ли dd-wrt или помидор отслеживать использование GB за период оплаты за устройство?

    Свободные интерфейсы и наследование в C #

    Как найти версию SSL / TLS в Java

    Давайте будем гением компьютера.