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;
}
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.
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 @"";
}
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
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
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
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 "";
}
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];
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.
Vous pouvez toujours utiliser [yourString uppercaseString] pour mettre les lettres en majuscule dans la description
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];
}
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
}
}
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
Changez %08x
en %08X
pour obtenir les caractères majuscules.
@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)