Domanda

Sto cercando un bel modo-di cacao per serializzare un oggetto NSData in una stringa esadecimale. L'idea è quella di serializzare l'deviceToken utilizzato per la notifica prima di inviarlo al mio server.

Ho la seguente implementazione, ma sto pensando che ci deve essere un modo più breve e più bello per farlo.

+ (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;
}
È stato utile?

Soluzione

Questa è una categoria applicato a NSData che ho scritto. Esso restituisce un NSString esadecimale che rappresenta il NSData, dove i dati possono essere di qualsiasi lunghezza. Restituisce una stringa vuota se NSData è vuoto.

NSData + Conversion.h

#import <Foundation/Foundation.h>

@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

Utilizzo:

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

Questa è "probabilmente" meglio che chiamare [someData description] e poi stripping di spazi, < 's, e>' s. Spogliarello personaggi si sente troppo "hacky". Inoltre non si sa mai se Apple cambierà la formattazione del -description di NSData in futuro.

Nota: persone che ho avuto mi raggiunga sulle licenze per il codice in questa risposta. Con la presente dedico il mio copyright nel codice che ho postato in questa risposta al dominio pubblico.

Altri suggerimenti

Ecco un altamente ottimizzato NSData metodo categoria per la generazione di una stringa esadecimale. Mentre la risposta di @ Dave Gallagher è sufficiente per una dimensione relativamente piccola, la memoria e CPU prestazioni deteriorarsi per grandi quantità di dati. Ho fatto il profilo questo con un file di 2MB sul mio confronto iPhone 5. Il tempo era 0,05 vs 12 secondi. Occupazione di memoria è trascurabile con questo metodo, mentre l'altro metodo è cresciuto mucchio di 70MBs!

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

Usando la proprietà di descrizione NSData non deve essere considerato un meccanismo accettabile per HEX codifica la stringa. Che la proprietà è solo per descrizione e può cambiare in qualsiasi momento. Come nota, pre-iOS, la descrizione della proprietà NSData non ha nemmeno restituirlo di dati in forma esadecimale.

Ci scusiamo per insistere sulla soluzione, ma è importante prendere l'energia per serializzare senza piggy-backing off un'API che si intende per qualcosa di diverso serializzazione dei dati.

@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

Versione Swift funzionale

Una nave di linea:

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

Ecco in una forma di estensione documentazione riutilizzabile e di auto:

extension NSData {
    func base16EncodedString(uppercase uppercase: Bool = false) -> String {
        let buffer = UnsafeBufferPointer<UInt8>(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("")
    }
}

In alternativa, l'uso reduce("", combine: +) invece di joinWithSeparator("") di essere visto come un maestro funzionale dai vostri coetanei.


Edit: ho cambiato String ($ 0, radix: 16) a String (formato: "% 02x", $ 0), perché i numeri di una cifra necessaria per avere un padding a zero

Ecco un modo veloce per eseguire la conversione:

BenchMark (tempo medio per una conversione 1024 byte dati ripetuta 100 volte):

Dave Gallagher: ~ 8.070 ms
NSProgrammer: ~ 0.077 ms
Peter: ~ 0,031 ms
Questo: ~ 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

La risposta di Peter portato su Swift

func hexString(data:NSData)->String{
    if data.length > 0 {
        let  hexChars = Array("0123456789abcdef".utf8) as [UInt8];
        let buf = UnsafeBufferPointer<UInt8>(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<UInt8>) -> String in
            let buf = UnsafeBufferPointer<UInt8>(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 "";
}

Swift 5

func hexString()->String{
    if count > 0 {
        let hexChars = Array("0123456789abcdef".utf8) as [UInt8];
        return withUnsafeBytes{ bytes->String in
            var output = [UInt8](repeating: 0, count: bytes.count*2 + 1);
            var ix:Int = 0;
            for b in bytes {
                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 "";
}

Avevo bisogno di risolvere questo problema e trovato le risposte qui molto utile, ma mi preoccupo per le prestazioni. La maggior parte di queste risposte coinvolgere copia dei dati alla rinfusa fuori NSData Così ho scritto quanto segue per fare la conversione con basso overhead:

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

Questo spazio pre-alloca nella stringa per l'intero risultato ed evita mai copiare il contenuto NSData con l'ausilio di enumerateByteRangesUsingBlock. Cambiando x in una x nella stringa di formato utilizzerà cifre esadecimali minuscole. Se non si desidera un separatore tra i byte è possibile ridurre l'istruzione

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

fino a solo

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

Avevo bisogno di una risposta che avrebbe funzionato per le stringhe di lunghezza variabile, ecco quello che ho fatto:

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

grandi opere come un'estensione per la classe NSString.

È sempre possibile utilizzare [StringaBella uppercaseString] per capitalizzare le lettere nella descrizione dei dati

Un modo migliore per serializzare / deserializzare NSData in NSString è quello di utilizzare la Google Toolbox per Mac Base64 encoder / decoder. Basta trascinare nel progetto App i file GTMBase64.m, GTMBase64.h e GTMDefines.h dalla Fondazione pacchetto e il fare qualcosa di simile

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

}

Ecco una soluzione che utilizza 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)

Cambia %08x per %08X per ottenere caratteri maiuscoli.

Swift + Proprietà.

Io preferisco avere la rappresentazione esadecimale come proprietà (lo stesso di bytes e description proprietà):

extension NSData {

    var hexString: String {

        let buffer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(self.bytes), count: self.length)
        return buffer.map { String(format: "%02x", $0) }.joinWithSeparator("")
    }

    var heXString: String {

        let buffer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(self.bytes), count: self.length)
        return buffer.map { String(format: "%02X", $0) }.joinWithSeparator("")
    }
}

Idea è preso in prestito da questo rispondere

[deviceToken description]

È necessario rimuovere gli spazi.

Personalmente base64 codificare i deviceToken, ma è una questione di gusti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top