web-dev-qa-db-fra.com

Meilleure façon de sérialiser un NSData en chaîne hexadécimale

Je cherche un moyen Nice-cacoa de sérialiser un objet NSData en chaîne hexadécimale. L'idée est de sérialiser le deviceToken utilisé pour la notification avant de l'envoyer à mon serveur.

J'ai l'implémentation suivante, mais je pense qu'il doit y avoir un moyen plus court et plus agréable de le faire.

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

C'est une catégorie appliquée à NSData que j'ai écrite. Il retourne une chaîne NSString hexadécimale représentant le NSData, où les données peuvent avoir n'importe quelle longueur. Retourne une chaîne vide si NSData est vide.

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

Usage:

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

C'est "probablement" mieux que d'appeler [someData description], puis d'effacer les espaces, les <et les>. Déshabiller des personnages semble trop "hacky". De plus, vous ne saurez jamais si Apple modifiera ultérieurement le formatage du -description de NSData.

NOTE: J'ai eu des gens me contacter à propos de la licence pour le code dans cette réponse. Par la présente, je dédie mes droits d'auteur du code que j'ai posté dans cette réponse au domaine public.

194
Dave Gallagher

Voici une méthode hautement optimisée NSData category pour générer une chaîne hexadécimale. Bien que la réponse de @Dave Gallagher soit suffisante pour une taille relativement petite, les performances de la mémoire et du processeur se détériorent pour de grandes quantités de données. Je profilais cela avec un fichier de 2 Mo sur mon iPhone 5. La comparaison des temps était de 0,05 vs 12 secondes. L'empreinte mémoire est négligeable avec cette méthode, tandis que l'autre méthode a porté le tas à 70 Mo!

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

L'utilisation de la propriété description de NSData ne doit pas être considérée comme un mécanisme acceptable pour le codage HEX de la chaîne. Cette propriété est pour la description seulement et peut changer à tout moment. En guise de remarque, avant iOS, la propriété de description NSData ne renvoyait même pas ses données sous forme hexadécimale.

Désolé de vouloir utiliser la solution, mais il est important de prendre le temps de la sérialiser sans utiliser une API destinée à autre chose que la sérialisation des données.

@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
15
NSProgrammer

Version Swift fonctionnelle

Bon mot:

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

Voici un formulaire d'extension réutilisable et auto-documentant:

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

Vous pouvez également utiliser reduce("", combine: +) au lieu de joinWithSeparator("") pour être perçu comme un maître fonctionnel par vos pairs.


Éditer: j'ai changé de chaîne ($ 0, radix: 16) en chaîne (format: "% 02x", $ 0), car un nombre à un chiffre devait avoir un zéro de remplissage

8
NiñoScript

Voici un moyen plus rapide de faire la conversion:

BenchMark (temps moyen pour une conversion de données de 1024 octets répétée 100 fois):

Dave Gallagher: ~ 8,070 ms
NSProgrammer: ~ 0,077 ms
Peter: ~ 0.031 ms
Celui-ci: ~ 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
8
Moose

La réponse de Peter portée à 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 "";
}
7
john07

J'avais besoin de résoudre ce problème et j'ai trouvé les réponses très utiles ici, mais je m'inquiète pour les performances. La plupart de ces réponses impliquent la copie en bloc des données hors de NSData. J'ai donc écrit ce qui suit pour effectuer la conversion avec un temps système faible:

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

Cela préalloue de l'espace dans la chaîne pour le résultat entier et évite de copier le contenu NSData en utilisant enumerateByteRangesUsingBlock. Changer le X en un x dans la chaîne de format utilisera des chiffres hexadécimaux minuscules. Si vous ne voulez pas de séparateur entre les octets, vous pouvez réduire l'instruction.

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

juste à

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

J'avais besoin d'une réponse qui fonctionnerait pour les chaînes de longueur variable, alors voici ce que j'ai fait:

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

Fonctionne très bien comme une extension pour la classe NSString.

2
BadPirate

Vous pouvez toujours utiliser [yourString uppercaseString] pour mettre les lettres en majuscule dans la description

1
Rostyslav Bachyk

Un meilleur moyen de sérialiser/désérialiser NSData dans NSString consiste à utiliser le codeur/décodeur Google Toolbox pour Mac Base64. Faites simplement glisser dans votre projet App les fichiers GTMBase64.m, GTMBase64.h et GTMDefines.h à partir du paquet 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];

}
1
loretoparisi

Voici une solution utilisant 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
    }

}
1
Alex

Swift + Propriété.

Je préfère avoir une représentation hexadécimale en tant que propriété (identique aux propriétés bytes et description):

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

Une idée est empruntée à ceci réponse

0
Avt

Changez %08x en %08X pour obtenir les caractères majuscules.

0
Dan Reese
@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)
0
Ramesh