NSData'yı onaltılık bir dizeye serileştirmenin en iyi yolu


101

NSData nesnesini onaltılık bir dizeye serileştirmek için güzel bir kakao yolu arıyorum. Fikir, sunucuma göndermeden önce bildirim için kullanılan deviceToken'ı seri hale getirmektir.

Aşağıdaki uygulamaya sahibim, ancak bunu yapmanın daha kısa ve daha güzel bir yolu olması gerektiğini düşünüyorum.

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

Yanıtlar:


206

Bu, yazdığım NSData'ya uygulanan bir kategoridir. Verilerin herhangi bir uzunlukta olabileceği NSData'yı temsil eden onaltılık bir NSString döndürür. NSData boşsa boş bir dize döndürür.

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

Kullanım:

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

Bu "muhtemelen" arayıp [someData description]sonra boşlukları, <'s ve>' leri çıkarmaktan daha iyidir . Karakterleri soymak sadece çok "beceriksiz" hissettiriyor. Ayrıca, Apple'ın gelecekte NSData'nın biçimlendirmesini değiştirip değiştirmeyeceğini asla bilemezsiniz -description.

NOT: Bu yanıttaki kodun lisanslanması konusunda bana ulaşan kişiler oldu. Bu yanıtta yayınladığım koddaki telif hakkımı kamu malı için ayırıyorum.


4
Güzel, ancak iki öneri: (1) AppendFormat'ın büyük veriler için daha verimli olduğunu düşünüyorum, çünkü bir ara NSString oluşturmaktan kaçınıyor ve (2)% x, işaretsiz uzun yerine işaretsiz bir int'i temsil ediyor, ancak fark zararsız.
svachalek

Karşı çıkmamak için, çünkü bu kullanımı kolay iyi bir çözüm ama benim 25 Ocak'taki çözümüm çok daha verimli. Performansı optimize edilmiş bir cevap arıyorsanız, bu cevaba bakın . Bu cevaba hoş ve anlaşılması kolay bir çözüm olarak oy vermek.
NSProgrammer

5
Bunun çalışması için (unsigned long) çevirmeyi kaldırmam ve biçim dizesi olarak @ "% 02hhx" kullanmam gerekti.
Anton


1
[hexString appendFormat:@"%02x", (unsigned int)dataBuffer[i]];çok daha iyi (daha küçük bellek ayak izi)
Marek R

31

Hex dizesi oluşturmak için oldukça optimize edilmiş bir NSData kategori yöntemi. @Dave Gallagher'ın cevabı nispeten küçük bir boyut için yeterliyken, bellek ve cpu performansı büyük miktarda veri için kötüleşiyor. Bunun profilini iPhone 5'imde 2MB'lık bir dosya ile oluşturdum. Zaman karşılaştırması 0.05 - 12 saniye idi. Bu yöntemle bellek ayak izi ihmal edilebilirken, diğer yöntem yığını 70 MB'a çıkardı!

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

Güzel bir @Peter - Yine de daha hızlı (sizinkinden çok olmayan) bir çözüm var - hemen altında;)
Moose

2
@Moose, lütfen hangi cevaptan bahsettiğinize daha kesin bir şekilde atıfta bulunun: oylar ve yeni cevaplar cevapların konumunu etkileyebilir. [düzenle: oh, tahmin edeyim, kendi cevabını kastediyorsun ...]
Cœur

1
Malloc boş kontrolü eklendi. Teşekkürler @ Cœur.
Peter

17

NSData'nın açıklama özelliğini kullanmak, dizeyi kodlayan HEX için kabul edilebilir bir mekanizma olarak görülmemelidir. Bu özellik yalnızca açıklama içindir ve herhangi bir zamanda değişebilir. Not olarak, iOS öncesi NSData açıklama özelliği verilerini onaltılık biçimde bile döndürmedi.

Çözümü zedelediğim için üzgünüz, ancak veri serileştirme dışında başka bir şey için amaçlanan bir API'yi desteklemeden serileştirmek için enerjiyi almak önemlidir.

@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

ancak, dönüşten önce serbest bırakmanız (hexChars) gerekir.
karim

3
@karim, bu yanlış. İnitWithCharactersNoCopy: length: freeWhenDone: öğesini kullanarak ve freeWhenDone YES değerine sahip olarak NSString, bu bayt arabelleğinin denetimini alır. Ücretsiz (hexChars) arama bir çökmeye neden olacaktır. NSString'in pahalı bir memcpy araması yapmak zorunda kalmayacağı için buradaki fayda önemli.
NSProgrammer

@NSProgrammer teşekkürler. NSSting başlatıcısını fark etmedim.
karim

Belgeler description, onaltılık kodlanmış bir dize döndürdüğünü belirtir , bu yüzden bu bana mantıklı geliyor.
Yaygın Olmayan

malloc tarafından döndürülen değerin potansiyel olarak boş olup olmadığını kontrol etmemiz gerekmez mi?
Cœur

10

İşte dönüşümü yapmanın daha hızlı bir yolu:

BenchMark (100 kez tekrarlanan 1024 baytlık veri dönüştürme için ortalama süre):

Dave Gallagher: ~ 8.070 ms
NSProgrammer: ~ 0.077 ms
Peter: ~ 0.031 ms
This One: ~ 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

1
Malloc kontrolünü nasıl uyguladığımı burada görebilirsiniz ( _hexStringyöntem): github.com/ZipArchive/ZipArchive/blob/master/SSZipArchive/…
Cœur

Referans için teşekkürler - BTW 'çok uzun' hoşuma gidiyor - Bu doğru, ama şimdi yazdım, herkes kopyalayabilir / yapıştırabilir - Şaka yapıyorum - Ben onu oluşturdum - zaten biliyordun :) Tam buradasınız uzun, sadece mikrosaniye kazanabileceğim her yere ulaşmaya çalışıyordum! Döngü yinelemelerini 2'ye böler. Ama zarafetten yoksun olduğunu kabul ediyorum. Bye
Moose

8

Fonksiyonel Swift versiyonu

Tek astar:

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

Yeniden kullanılabilir ve kendi kendini belgeleyen bir uzantı formunda:

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

Alternatif olarak, meslektaşlarınız tarafından işlevsel bir usta olarak görülmek reduce("", combine: +)yerine kullanın joinWithSeparator("").


Düzenleme: String'i ($ 0, radix: 16) String (format: "% 02x", $ 0) olarak değiştirdim çünkü bir basamaklı sayıların sıfır dolgusu olması için gerekli


7

Peter'ın cevabı Swift'e taşındı

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

Swift 5

func hexString()->String{
    if count > 0 {
        let hexChars = Array("0123456789abcdef".utf8) as [UInt8];
        return withUnsafeBytes{ bytes->String in
            var output = [UInt8](repeating: 0, count: bytes.count*2 + 1);
            var ix:Int = 0;
            for b in bytes {
                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 "";
}

4

Bu sorunu çözmem gerekiyordu ve buradaki cevapları çok faydalı buldum, ancak performans konusunda endişeliyim. Bu yanıtların çoğu, verilerin NSData'dan toplu olarak kopyalanmasını içerir, bu yüzden dönüşümü düşük ek yük ile yapmak için aşağıdakileri yazdım:

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

Bu, tüm sonuç için dizedeki alanı önceden ayırır ve enumerateByteRangesUsingBlock kullanarak NSData içeriklerinin her zaman kopyalanmasını önler. X'i biçim dizesindeki bir x olarak değiştirmek, küçük harfli onaltılık rakamlar kullanacaktır. Baytlar arasında bir ayırıcı istemiyorsanız, ifadeyi azaltabilirsiniz.

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

aşağıya

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

2
Bayt değerini almak için indeksin ayarlanması gerektiğine inanıyorum, çünkü, NSRangedaha büyük olanın tek bir bitişik bölümünü temsil eden NSDatadaha küçük bayt tamponunda (sağlanan bloğun ilk parametresi) değil , daha büyük temsil içindeki aralığı gösterir . Böylece, bayt arabelleğinin boyutunu yansıtır, ancak daha büyük olanın içindeki konumdur . Bu nedenle, baytı almak için değil , basitçe kullanmak istersiniz . enumerateByteRangesUsingBlockNSDatabyteRange.lengthbyteRange.locationNSDataoffsetbyteRange.location + offset
Rob

1
@Rob Teşekkürler, ne demek istediğinizi anlıyorum ve kodu düzelttim
John Stephen 14

1
Sadece tek kullanmak için şu deyimi aşağı değiştirirseniz appendFormatmuhtemelen de değişmelidir self.length * 3içinself.length * 2
T. Colligan

1

Değişken uzunluklu dizeler için işe yarayacak bir cevaba ihtiyacım vardı, işte yaptığım şey:

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

NSString sınıfı için bir uzantı olarak harika çalışıyor.


1
Apple açıklamayı temsil etme şeklini değiştirirse ne olur?
Brenden

1
iOS13'te açıklama yöntemi farklı bir biçim döndürür.
nacho4d

1

Veri açıklamasında harfleri büyük harf yapmak için her zaman [yourString uppercaseString] kullanabilirsiniz


1

NSData'yı NSString'e serileştirmenin / serisini kaldırmanın daha iyi bir yolu, Google Toolbox for Mac Base64 kodlayıcı / kod çözücüyü kullanmaktır. Paket Foundation'dan GTMBase64.m, GTMBase64.he GTMDefines.h dosyalarını Uygulama Projenize sürükleyin ve aşağıdaki gibi bir şey yapın:

/**
 * 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];

}

Kaynak koduna bakıldığında, bunu sağlayan sınıfın artık GTMStringEncoding olduğu görülmektedir. Henüz denemedim ama bu soruya yeni ve harika bir çözüm gibi görünüyor.
sarfata

1
Mac OS X 10.6 / iOS 4.0'dan itibaren NSData, Base-64 kodlama yapmaktadır. string = [data base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]
JRC

@jrc bu doğrudur, ancak Base-64'te gerçek çalışma dizelerini kodlamayı düşünün. GTMBase64 # webSafeEncodeData'da olduğu gibi iOS / MacOS'ta olmayan "web güvenli" kodlamayla uğraşmak zorundasınız. Ayrıca Base64 "dolgusu" eklemeniz / kaldırmanız gerekebilir, bu nedenle bu seçeneğe de sahipsiniz: GTMBase64 # stringByWebSafeEncodingData: (NSData *) veri dolgulu: (BOOL) padded;
loretoparisi

1

İşte Swift 3 kullanan bir çözüm

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
    }

}

0
@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

Büyük karakter elde %08xetmek %08Xiçin olarak değiştirin .


6
herhangi bir bağlam eklemediğiniz için bu yorum olarak daha iyi olur. Sadece diyorsun
Brenden

0

Swift + Mülkiyet.

Özellik olarak onaltılık gösterimi tercih ederim ( bytesve descriptionözelliklerle aynı ):

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

Fikir bu cevaptan ödünç alınmıştır


-4
[deviceToken description]

Boşlukları kaldırmanız gerekecek.

Şahsen ben base64kodluyorum deviceTokenama bu bir zevk meselesi.


Bu aynı sonucu almaz. açıklama döndürür: <2cf56d5d 2fab0a47 ... 7738ce77 7e791759> Ararken: 2CF56D5D2FAB0A47 .... 7738CE777E791759
sarfata
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.