Quand j'appelle -description
sur un objet NSData
, je vois une jolie chaîne hexadécimale des octets de l'objet NSData
comme:
<f6e7cd28 0fc5b5d4 88f8394b af216506 bc1bba86 4d5b483d>
Je voudrais obtenir cette représentation des données (moins les guillemets lt/gt) dans un NSString
en mémoire afin que je puisse travailler avec lui .. Je préfère ne pas appeler -[NSData description]
et puis juste couper les citations lt/gt (parce que je suppose que ce n'est pas un aspect garanti de l'interface publique de NSData
et qu'il est sujet à changement à l'avenir).
Quelle est la manière la plus simple d'obtenir cette représentation d'un objet NSData
dans un objet NSString
(autre que d'appeler -description
)?
Gardez à l'esprit que toute solution String(format: ...)
sera terriblement lente (pour les données volumineuses)
NSData *data = ...;
NSUInteger capacity = data.length * 2;
NSMutableString *sbuf = [NSMutableString stringWithCapacity:capacity];
const unsigned char *buf = data.bytes;
NSInteger i;
for (i=0; i<data.length; ++i) {
[sbuf appendFormat:@"%02X", (NSUInteger)buf[i]];
}
Si vous avez besoin de quelque chose plus performant essayez ceci:
static inline char itoh(int i) {
if (i > 9) return 'A' + (i - 10);
return '0' + i;
}
NSString * NSDataToHex(NSData *data) {
NSUInteger i, len;
unsigned char *buf, *bytes;
len = data.length;
bytes = (unsigned char*)data.bytes;
buf = malloc(len*2);
for (i=0; i<len; i++) {
buf[i*2] = itoh((bytes[i] >> 4) & 0xF);
buf[i*2+1] = itoh(bytes[i] & 0xF);
}
return [[NSString alloc] initWithBytesNoCopy:buf
length:len*2
encoding:NSASCIIStringEncoding
freeWhenDone:YES];
}
Version Swift 4.2
extension Data {
var hexString: String? {
return withUnsafeBytes { (bytes: UnsafePointer<UInt8>) in
let charA = UInt8(UnicodeScalar("a").value)
let char0 = UInt8(UnicodeScalar("0").value)
func itoh(_ value: UInt8) -> UInt8 {
return (value > 9) ? (charA + value - 10) : (char0 + value)
}
let hexLen = count * 2
let ptr = UnsafeMutablePointer<UInt8>.allocate(capacity: hexLen)
for i in 0 ..< count {
ptr[i*2] = itoh((bytes[i] >> 4) & 0xF)
ptr[i*2+1] = itoh(bytes[i] & 0xF)
}
return String(bytesNoCopy: ptr,
length: hexLen,
encoding: .utf8,
freeWhenDone: true)
}
}
}
Je suis d'accord sur la solution pas pour appeler description
qui est à réserver pour le débogage, donc bon point et bonne question :)
La solution la plus simple consiste à parcourir les octets de NSData
et à construire la NSString à partir de celle-ci. Utilisation [yourData bytes]
pour accéder aux octets et créer la chaîne dans un NSMutableString
.
Voici un exemple en l'implémentant à l'aide d'une catégorie de NSData
@interface NSData(Hex)
-(NSString*)hexRepresentationWithSpaces_AS:(BOOL)spaces;
@end
@implementation NSData(Hex)
-(NSString*)hexRepresentationWithSpaces_AS:(BOOL)spaces
{
const unsigned char* bytes = (const unsigned char*)[self bytes];
NSUInteger nbBytes = [self length];
//If spaces is true, insert a space every this many input bytes (twice this many output characters).
static const NSUInteger spaceEveryThisManyBytes = 4UL;
//If spaces is true, insert a line-break instead of a space every this many spaces.
static const NSUInteger lineBreakEveryThisManySpaces = 4UL;
const NSUInteger lineBreakEveryThisManyBytes = spaceEveryThisManyBytes * lineBreakEveryThisManySpaces;
NSUInteger strLen = 2*nbBytes + (spaces ? nbBytes/spaceEveryThisManyBytes : 0);
NSMutableString* hex = [[NSMutableString alloc] initWithCapacity:strLen];
for(NSUInteger i=0; i<nbBytes; ) {
[hex appendFormat:@"%02X", bytes[i]];
//We need to increment here so that the every-n-bytes computations are right.
++i;
if (spaces) {
if (i % lineBreakEveryThisManyBytes == 0) [hex appendString:@"\n"];
else if (i % spaceEveryThisManyBytes == 0) [hex appendString:@" "];
}
}
return [hex autorelease];
}
@end
Usage:
NSData* data = ...
NSString* hex = [data hexRepresentationWithSpaces_AS:YES];
Je voulais juste ajouter que la méthode de @ PassKits peut être écrite très élégamment en utilisant Swift 3 puisque Data
est maintenant une collection.
extension Data {
var hex: String {
var hexString = ""
for byte in self {
hexString += String(format: "%02X", byte)
}
return hexString
}
}
Ou ...
extension Data {
var hex: String {
return self.map { b in String(format: "%02X", b) }.joined()
}
}
Ou même ...
extension Data {
var hex: String {
return self.reduce("") { string, byte in
string + String(format: "%02X", byte)
}
}
}
J'ai bien aimé la réponse de @ Erik_Aigner. Je l'ai juste un peu refactorisé:
NSData *data = [NSMutableData dataWithBytes:"acani" length:5];
NSUInteger dataLength = [data length];
NSMutableString *string = [NSMutableString stringWithCapacity:dataLength*2];
const unsigned char *dataBytes = [data bytes];
for (NSInteger idx = 0; idx < dataLength; ++idx) {
[string appendFormat:@"%02x", dataBytes[idx]];
}
Dans Swift vous pouvez créer une extension.
extension NSData {
func toHexString() -> String {
var hexString: String = ""
let dataBytes = UnsafePointer<CUnsignedChar>(self.bytes)
for (var i: Int=0; i<self.length; ++i) {
hexString += String(format: "%02X", dataBytes[i])
}
return hexString
}
}
Ensuite, vous pouvez simplement utiliser:
let keyData: NSData = NSData(bytes: [0x00, 0xFF], length: 2)
let hexString = keyData.toHexString()
println("\(hexString)") // Outputs 00FF
Malheureusement, il n'y a aucun moyen intégré de produire de l'hex à partir d'un NSData, mais c'est assez facile à faire vous-même. Le moyen le plus simple est de simplement passer des octets successifs dans sprintf ("% 02x") et de les accumuler dans une NSMutableString. Un moyen plus rapide serait de construire une table de recherche qui mappe 4 bits en un caractère hexadécimal, puis de passer des nybbles successifs dans cette table.
Bien que ce ne soit pas le moyen le plus efficace de le faire, si vous le faites pour le débogage, SSCrypto a une catégorie sur NSData qui contient deux méthodes pour le faire (une pour créer une NSString des valeurs d'octets brutes et une qui en montre une plus jolie représentation).
Voyant qu'il y a un Swift 1.2 extrait dans les commentaires, voici la Swift 2 version car le style C pour les boucles est désormais obsolète. Gist = avec MIT licence et deux tests unitaires simples si vous vous souciez.
Voici le code pour votre commodité:
import Foundation
extension NSData {
var hexString: String {
let pointer = UnsafePointer<UInt8>(bytes)
let array = getByteArray(pointer)
return array.reduce("") { (result, byte) -> String in
result.stringByAppendingString(String(format: "%02x", byte))
}
}
private func getByteArray(pointer: UnsafePointer<UInt8>) -> [UInt8] {
let buffer = UnsafeBufferPointer<UInt8>(start: pointer, count: length)
return [UInt8](buffer)
}
}