NSDataを16進文字列にシリアル化する最良の方法


101

NSDataオブジェクトを16進数の文字列にシリアル化するための、素敵なココアの方法を探しています。アイデアは、サーバーに送信する前に、通知に使用されるdeviceTokenをシリアル化することです。

私は次の実装をしていますが、それを行うにはもっと短くて良い方法がいくつかあるはずだと思っています。

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

回答:


206

これは私が書いたNSDataに適用されるカテゴリです。NSDataを表す16進数のNSStringを返します。データは任意の長さにできます。NSDataが空の場合、空の文字列を返します。

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

使用法:

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

これは、「おそらく」呼び出して[someData description]からスペース、<、および>を取り除くよりも優れています。文字を取り除くことは、あまりにも「ハック」に感じられます。さらに-description、将来AppleがNSDataのフォーマットを変更するかどうかはわかりません。

注:この回答のコードのライセンスについて、他の人に連絡してもらいました。私はこれにより、この回答で投稿したコードに著作権を公開ドメインに捧げます。


4
いいが、2つの提案:(1)中間のNSStringの作成を回避するため、appendFormatは大きなデータに対してより効率的であると思います。(2)%xはunsigned longではなくunsigned intを表しますが、違いは無害です。
svachalek

これは使いやすい優れたソリューションですが、1月25日の私のソリューションの方がはるかに効率的です。パフォーマンスが最適化された回答を探している場合は、その回答を参照してください。この答えを、わかりやすくてわかりやすい解決策として支持してください。
NSProgrammer 2012

5
これを機能させるには、(unsigned long)キャストを削除し、@ "%02hhx"をフォーマット文字列として使用する必要がありました。
アントン

1
そうです、developer.apple.com / "%02lx"(unsigned int)@"%02hhx"
library / ios / documentation / cocoa / conceptual /…

1
[hexString appendFormat:@"%02x", (unsigned int)dataBuffer[i]];はるかに良い(メモリフットプリントが小さい)
Marek R

31

16進文字列を生成するために高度に最適化されたNSDataカテゴリメソッドを次に示します。@Dave Gallagherの答えは比較的小さなサイズには十分ですが、大量のデータに対してメモリとCPUのパフォーマンスは低下します。iPhone 5で2MBのファイルを使用してこれをプロファイリングしました。時間の比較は0.05秒と12秒でした。この方法ではメモリフットプリントは無視できますが、他の方法ではヒープが70MBに増加します。

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

@Peterの素敵な1つ-ただし、さらに高速な(あなたのソリューションほどではない)解決策があります-すぐ下です;)
Moose

1
@Moose、あなたが話している答えをより正確に参照してください:投票と新しい答えは、答えの配置に影響を与える可能性があります。[編集:ああ、私は推測してみましょう、あなたがあなた自身の答えを意味...]
・クール

1
malloc nullチェックが追加されました。@Cœur、ありがとう。
Peter

17

NSDataのdescriptionプロパティを使用することは、文字列をHEXエンコードするための許容可能なメカニズムと見なされるべきではありません。このプロパティは説明のみを目的としており、いつでも変更できます。注、iOS以前では、NSData記述プロパティは16進形式でデータを返しませんでした。

ソリューションに手を出して申し訳ありませんが、データのシリアル化以外の何かのために意図されているAPIをピギーバックすることなく、それをシリアル化するためのエネルギーを取ることが重要です。

@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

ただし、戻る前にfree(hexChars)する必要があります。
karim 2014

3
@karim、それは間違っています。initWithCharactersNoCopy:length:freeWhenDone:を使用し、freeWhenDoneをYESにすることにより、NSStringはそのバイトバッファを制御します。free(hexChars)を呼び出すとクラッシュします。NSStringは高価なmemcpy呼び出しを行う必要がないため、ここでの利点は大きなものです。
NSProgrammer 2014

@NSProgrammerありがとう。NSStingイニシャライザに気づきませんでした。
karim 2014年

ドキュメントにdescriptionは、16進数でエンコードされた文字列を返すと記載されているので、それは私には理にかなっているようです。
珍しい

mallocの戻り値がnullの可能性がないかどうかを確認する必要はありませんか?
2017年

9

変換を行うためのより高速な方法を次に示します。

BenchMark(1024バイトのデータ変換が100回繰り返された平均時間):

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のチェックを実施してどのように見ることができる_hexString方法):github.com/ZipArchive/ZipArchive/blob/master/SSZipArchive/...
・クール

参考にしてください-ところで私は「長すぎる」が好きです-それは本当です、しかし私はそれをタイプしたので、誰でもコピー/貼り付けできます-私は冗談です-私はそれを生成しました-あなたはすでに知っていました:)あなたは正しいです長く、私はマイクロ秒を獲得できるあらゆる場所を攻撃しようとしていました!ループの繰り返しを2で割ります。しかし、エレガントさは欠けています。さようなら
ムース

8

機能的なSwiftバージョン

一発ギャグ:

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

これは、再利用可能な自己文書化拡張形式です。

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

または、reduce("", combine: +)代わりにjoinWithSeparator("")を使用して、ピアから機能マスターとして表示されます。


編集:1桁の数値にパディングゼロを含める必要があるため、String($ 0、radix:16)をString(format: "%02x"、$ 0)に変更しました


7

ピーターの回答をスウィフトに移植

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

スウィフト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

私はこの問題を解決する必要があり、ここでの回答は非常に役立つと思いましたが、パフォーマンスについて心配しています。これらの答えのほとんどはNSDataからデータを一括でコピーすることを含むため、以下のように記述して、オーバーヘッドを抑えて変換を行います。

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

これにより、結果全体に文字列内のスペースが事前に割り当てられ、enumerateByteRangesUsingBlockを使用してNSDataの内容がコピーされることがなくなります。フォーマット文字列でXをxに変更すると、小文字の16進数が使用されます。バイト間の区切り文字が不要な場合は、ステートメントを減らすことができます

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

ちょうどにダウン

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

2
は、バイト値を取得するためのインデックスの調整が必要であると考えています。これはNSRange、が大きいNSData表現のenumerateByteRangesUsingBlock連続する部分を表す小さいバイトバッファー(に供給されるブロックの最初のパラメーター)内ではなく、大きい表現内の範囲を示すためNSDataです。したがって、byteRange.lengthはバイトバッファのサイズを反映しますが、byteRange.locationはより大きな内の場所NSDataです。したがって、バイトを取得するためにではoffsetなくbyteRange.location + offset、単に使用する必要があります。
Rob

1
@ロブありがとう、私はあなたが何を意味するのかを理解し、コードを調整しました
John Stephen

1
あなたは1つだけを使用するまでの文を変更した場合appendFormat、あなたはおそらくも変更する必要がありますself.length * 3self.length * 2
T.コリガン

1

可変長の文字列で機能する答えが必要だったので、次のようにします。

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

NSStringクラスの拡張として機能します。


1
Appleが説明の表現方法を変更するとどうなりますか?
ブレンデン2013年

1
iOS13では、descriptionメソッドは別のフォーマットを返します。
nacho4d


1

NSDataをNSStringにシリアル化/非シリアル化するより良い方法は、Mac Tool64 for MacGoogle Toolboxエンコーダー/デコーダーを使用することです。アプリプロジェクトにファイルGTMBase64.m、GTMBase64.he GTMDefines.hをファイル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];

}

ソースコードを見ると、それを提供するクラスは現在GTMStringEncodingであるようです。私はそれを試していませんが、この質問に対する素晴らしい新しい解決策のようです。
sarfata

1
Mac OS X 10.6 / iOS 4.0以降、NSDataはBase-64エンコーディングを行います。string = [data base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]
jrc

@jrcは正しいですが、実際の作業文字列をBase-64でエンコードすることを検討してください。GTMBase64#webSafeEncodeDataのように、iOS / MacOSにはない「Webセーフ」エンコーディングを処理する必要があります。また、Base64の「パディング」を追加/削除する必要がある場合があるため、このオプションも使用できます。GTMBase64#stringByWebSafeEncodingData:(NSData *)data padded:(BOOL)padded;
loretoparisi 2015

1

これは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
    }

}

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

Swift +プロパティ。

プロパティとして16進数表現を使用することを好みます(bytesおよび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("")
    }
}

この答えからアイデアが借りられます


-4
[deviceToken description]

スペースを削除する必要があります。

個人的にはをbase64エンコードしてdeviceTokenいますが、これは好みの問題です。


これは同じ結果にはなりません。説明が返されます:<2cf56d5d 2fab0a47 ... 7738ce77 7e791759>探している間:2CF56D5D2FAB0A47 .... 7738CE777E791759
sarfata
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.