Как преобразовать NSData в шестнадцатеричную строку NSString? - PullRequest
52 голосов
/ 22 сентября 2011

Когда я вызываю -description для объекта NSData, я вижу красивую шестнадцатеричную строку байтов NSData объекта, например:

<f6e7cd28 0fc5b5d4 88f8394b af216506 bc1bba86 4d5b483d>

Я хотел бы получить это представлениеданные (за исключением кавычек lt / gt) в оперативную память NSString, чтобы я мог работать с ними .. Я бы предпочел не вызывать -[NSData description], а затем просто обрезать кавычки lt / gt (потому что я предполагаю, что этоне является гарантированным аспектом общедоступного интерфейса NSData и может быть изменено в будущем).

Какой самый простой способ получить это представление объекта NSData в объекте NSString (кроме вызова -description)?

Ответы [ 8 ]

79 голосов
/ 22 сентября 2011

Имейте в виду, что любое решение String(format: ...) будет очень медленным (для больших данных)

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

Если вам нужно что-то более производительное , попробуйте это:

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 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)
        }
    }
}
28 голосов
/ 22 сентября 2011

Я согласен с решением , а не для вызова description, который должен быть зарезервирован для отладки, поэтому хороший момент и хороший вопрос

Самое простое решение - зациклить байты NSData и создать из него строку NSString. Используйте [yourData bytes] для доступа к байтам и встраивайте строку в NSMutableString.

Вот пример реализации этого с использованием категории 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

Использование:

NSData* data = ...
NSString* hex = [data hexRepresentationWithSpaces_AS:YES];
20 голосов
/ 30 июня 2016

Просто хотел добавить, что метод @ PassKits можно написать очень элегантно, используя Swift 3, поскольку Data теперь является коллекцией.

extension Data { 
    var hex: String {
        var hexString = ""
        for byte in self {
            hexString += String(format: "%02X", byte)
        }

        return hexString
    }
}

Или ...

extension Data {
    var hex: String {
        return self.map { b in String(format: "%02X", b) }.joined()
    }
}

Или даже ...

extension Data {
    var hex: String {
        return self.reduce("") { string, byte in
            string + String(format: "%02X", byte)
        }
    }
}
20 голосов
/ 16 сентября 2012

Мне больше всего понравился ответ @ Erik_Aigner.Я просто немного изменил рефакторинг:

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]];
}
8 голосов
/ 27 ноября 2014

В Swift вы можете создать расширение.

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

Тогда вы можете просто использовать:

let keyData: NSData = NSData(bytes: [0x00, 0xFF], length: 2)

let hexString = keyData.toHexString()
println("\(hexString)") // Outputs 00FF
3 голосов
/ 22 сентября 2011

К сожалению, нет встроенного способа получения гекса из NSData, но это довольно легко сделать самостоятельно. Простой способ - просто передать последовательные байты в sprintf ("% 02x") и накапливать их в NSMutableString. Более быстрый способ - создать справочную таблицу, которая отображает 4 бита в шестнадцатеричный символ, а затем передает последовательные фрагменты в эту таблицу.

1 голос
/ 04 февраля 2016

Видя, что в комментариях есть фрагмент Swift 1.2, вот версия Swift 2, так как стиль C для циклов устарел. Gist с лицензией MIT и двумя простыми юнит-тестами, если вам это нужно.

Вот код для вашего удобства:

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)
  }
}
1 голос
/ 22 сентября 2011

Хотя это может быть не самый эффективный способ сделать это, но если вы делаете это для отладки, у SSCrypto есть категория для NSData, которая содержит два метода для этого (один для создания строки NSSt из необработанных байтовых значений,и тот, который показывает более красивое представление этого).

http://www.septicus.com/SSCrypto/trunk/SSCrypto.m

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...