How to convert an NSData into an NSString Hex string?
Solution 1:
Keep in mind that any String(format: ...)
solution will be terribly slow (for large data)
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]];
}
If you need something more performant try this:
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];
}
Swift version
private extension Data {
var hexadecimalString: String {
let charA: UInt8 = 0x61
let char0: UInt8 = 0x30
func byteToChar(_ b: UInt8) -> Character {
Character(UnicodeScalar(b > 9 ? charA + b - 10 : char0 + b))
}
let hexChars = flatMap {[
byteToChar(($0 >> 4) & 0xF),
byteToChar($0 & 0xF)
]}
return String(hexChars)
}
}
Solution 2:
I agree on the solution not to call description
which is to be reserved for debugging, so good point and good question :)
The easiest solution is to loop thru the bytes of the NSData
and construct the NSString from it. Use [yourData bytes]
to access the bytes, and build the string into an NSMutableString
.
Here is an example by implementing this using a category of 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];
Solution 3:
Just wanted to add that @PassKits's method can be written very elegantly using Swift 3 since Data
now is a collection.
extension Data {
var hex: String {
var hexString = ""
for byte in self {
hexString += String(format: "%02X", byte)
}
return hexString
}
}
Or ...
extension Data {
var hex: String {
return self.map { b in String(format: "%02X", b) }.joined()
}
}
Or even ...
extension Data {
var hex: String {
return self.reduce("") { string, byte in
string + String(format: "%02X", byte)
}
}
}
Solution 4:
I liked @Erik_Aigner's answer the best. I just refactored it a bit:
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]];
}