Melhor maneira de serializar um NSData em uma string hexadeximal

Eu estou procurando uma maneira agradável-cacau para serializar um object NSData em uma seqüência hexadecimal. A idéia é serializar o deviceToken usado para notificação antes de enviá-lo para o meu servidor.

Eu tenho a seguinte implementação, mas estou pensando que deve haver alguma maneira mais curta e mais agradável de fazê-lo.

+ (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; } 

   

    Esta é uma categoria aplicada ao NSData que eu escrevi. Ele retorna um NSString hexadecimal representando o NSData, onde os dados podem ter qualquer tamanho. Retorna uma string vazia se NSData estiver vazio.

    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 

    Uso:

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

    Isso é "provavelmente" melhor do que chamar [someData description] e, em seguida, remover os espaços, e> 's. Stripping personagens só se sente muito "hacky". Além disso, você nunca sabe se a Apple irá alterar a formatação da descrição do -description no futuro.

    OBSERVAÇÃO: algumas pessoas me contataram sobre o licenciamento do código nesta resposta. Dedico meus direitos autorais no código que eu postei nesta resposta para o domínio público.

    Aqui está um método de categoria NSData altamente otimizado para gerar uma string hexadecimal. Embora a resposta da @Dave Gallagher seja suficiente para um tamanho relativamente pequeno, a memory e o desempenho da CPU se deterioram para grandes quantidades de dados. Eu perfilei isso com um arquivo de 2MB no meu iPhone 5. Comparação de tempo foi de 0,05 vs 12 segundos. A pegada de memory é insignificante com este método, enquanto o outro método aumentou o heap para 70MB!

     - (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 @""; } 

    O uso da propriedade description do NSData não deve ser considerado um mecanismo aceitável para a codificação HEX da string. Essa propriedade é apenas para descrição e pode ser alterada a qualquer momento. Como nota, pré-iOS, a propriedade de descrição NSData nem retornou seus dados em formato hexadecimal.

    Desculpe por insistir na solução, mas é importante usar a energia para serializá-la sem fazer um backup de uma API destinada a outra coisa que não a serialização de dados.

     @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 

    Aqui está uma maneira mais rápida de fazer a conversão:

    BenchMark (tempo médio para uma conversão de dados de 1024 bytes repetida 100 vezes):

    Dave Gallagher: ~ 8.070 ms
    NSProgrammer: ~ 0,077 ms
    Pedro: ~ 0,031 ms
    Este um: ~ 0,017 ms

     @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 

    Versão Swift Funcional

    Um forro:

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

    Aqui está um formulário de extensão reutilizável e auto-documentável:

     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("") } } 

    Como alternativa, use reduce("", combine: +) vez de joinWithSeparator("") para ser visto como um master funcional por seus colegas.


    Edit: Eu mudei String ($ 0, radix: 16) para String (formato: “% 02x”, $ 0), porque um dígito números necessários para ter um zero de preenchimento

    A resposta de Peter foi portada para Swift

     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 ""; } 

    Eu precisava resolver esse problema e achei as respostas muito úteis aqui, mas me preocupo com o desempenho. A maioria dessas respostas envolve copiar os dados em grande quantidade do NSData, então escrevi o seguinte para fazer a conversão com pouca sobrecarga:

     @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; } 

    Isso pré-aloca espaço na seqüência de caracteres para todo o resultado e evita sempre copiar o conteúdo NSData usando enumerateByteRangesUsingBlock. Alterar o X para um x na string de formato usará dígitos hexadecimais minúsculos. Se você não quer um separador entre os bytes, você pode reduzir a declaração

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

    até apenas

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

    Eu precisava de uma resposta que funcionasse para seqüências de comprimento variável, então aqui está o que eu fiz:

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

    Funciona muito bem como uma extensão para a class NSString.

    Você sempre pode usar [yourString uppercaseString] para capitalizar letras na descrição de dados

    Uma maneira melhor de serializar / desserializar o NSData em NSString é usar o codificador / decodificador do Google Toolbox para Mac Base64. Basta arrastar para o seu projeto do aplicativo os arquivos GTMBase64.m, GTMBase64.he GTMDefines.h do pacote Foundation e fazer algo como

     /** * 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]; } 

    Aqui está uma solução usando o 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) 

    Altere %08x para %08X para obter caracteres maiúsculos.

    Swift + Propriedade.

    Eu prefiro ter representação hex como propriedade (o mesmo que bytes e propriedades de 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("") } } 

    A ideia é emprestada desta resposta

     [deviceToken description] 

    Você precisará remover os espaços.

    Pessoalmente eu deviceToken base64 o deviceToken , mas é uma questão de gosto.