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]공백, < 's 및>를 호출 한 다음 제거하는 것보다 "아마"더 낫습니다 . 벗기는 문자는 너무 "해키"느낌이 듭니다. 또한 Apple이 -description미래 에 NSData의 형식을 변경할지 알 수 없습니다 .

참고 : 이 답변의 코드에 대한 라이선스에 대해 사람들이 저에게 연락했습니다. 이로써 나는이 답변에 게시 한 코드에 내 저작권을 공개 도메인에 바칩니다.


4
좋지만 두 가지 제안 : (1) appendFormat은 중간 NSString 생성을 피하고 (2) % x는 그 차이가 무해하지만 unsigned long보다는 unsigned int를 나타 내기 때문에 대용량 데이터에 더 효율적이라고 생각합니다.
svachalek

이것은 사용하기 쉬운 좋은 솔루션이지만 1 월 25 일에 내 솔루션 이 훨씬 더 효율적 이기 때문에 반대자 가 아닙니다. 성능에 최적화 된 답변을 찾고 있다면 해당 답변을 참조하십시오 . 이 답변을 멋지고 이해하기 쉬운 솔루션으로 찬성합니다.
NSProgrammer 2012 년

5
이 작업을 수행하려면 (부호없는 long) 캐스트를 제거하고 @ "% 02hhx"를 형식 문자열로 사용해야했습니다.
Anton

1
오른쪽, 당 developer.apple.com/library/ios/documentation/cocoa/conceptual/... 형식이되어야합니다 "%02lx"해당 캐스트, 또는 캐스트 (unsigned int), 또는 주조 및 사용 드롭 @"%02hhx":
qix

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

멋진 하나의 @ 피터는 - 그러나 더 빠른 (별로 당신의 것보다) 솔루션이있다 - 바로 아래)
무스

1
@Moose, 어떤 답변에 대해 더 정확하게 언급하십시오. 투표 및 새 답변이 답변의 위치에 영향을 미칠 수 있습니다. [편집 : 오, 추측 해 보겠습니다. 당신은 자신의 대답을 의미합니다 ...]
Cœur

1
malloc null 검사를 추가했습니다. 감사합니다 @ Cœur.
피터

17

NSData의 설명 속성을 사용하는 것은 문자열을 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

3
@karim, 틀 렸습니다. initWithCharactersNoCopy : length : freeWhenDone :을 사용하고 freeWhenDone을 ​​YES로 설정하면 NSString이 해당 바이트 버퍼를 제어합니다. free (hexChars)를 호출하면 충돌이 발생합니다. NSString이 값 비싼 memcpy 호출을 할 필요가 없기 때문에 여기서의 이점은 상당합니다.
NSProgrammer 2014

@NSProgrammer 감사합니다. NSSting 이니셜 라이저를 알아 차리지 못했습니다.
카림

문서 description에는 16 진수 인코딩 문자열 을 반환한다고 나와 있으므로 나에게 합리적입니다.
Uncommon

malloc 반환 값이 잠재적으로 null인지 확인해야하지 않습니까?
Cœur

9

변환을 수행하는 더 빠른 방법은 다음과 같습니다.

BenchMark (1024 바이트 데이터 변환의 평균 시간이 100 회 반복됨) :

데이브 갤러거 : ~
8.070ms NS 프로그래머 : ~ 0.077ms
Peter : ~ 0.031ms
This One : ~ 0.017ms

@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/…
Cœur

참고 해주셔서 감사합니다-BTW 저는 '너무 긴'것을 좋아합니다-사실입니다.하지만 이제 제가 입력 했으니 누구나 복사 / 붙여 넣기를 할 수 있습니다-농담이에요-제가 생성했습니다-당신은 이미 알고 있습니다 :) 당신이 옳았습니다 긴 시간 동안, 나는 마이크로 초를 이길 수있는 모든 곳을 공격하려고했습니다! 루프 반복을 2로 나눕니다. 그러나 우아함이 부족하다는 것을 인정합니다. Bye
Moose

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("")하십시오.


편집 : String ($ 0, radix : 16)을 String (format : "% 02x", $ 0)으로 변경했습니다. 한 자리 숫자는 패딩 0이 필요하기 때문입니다.


7

Peter의 대답은 Swift로 이식되었습니다.

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

스위프트 3

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더 큰 .NET의 단일 연속 부분을 나타내는 NSData더 작은 바이트 버퍼 (에 제공된 블록의 첫 번째 매개 변수)가 아닌 더 큰 표현 내의 범위를 나타 내기 때문 입니다. 따라서 는 바이트 버퍼의 크기를 반영하지만 더 큰 . 따라서 바이트를 검색하는 데 사용하는 것이 아니라 단순히 사용하려고합니다 . enumerateByteRangesUsingBlockNSDatabyteRange.lengthbyteRange.locationNSDataoffsetbyteRange.location + offset
Rob

1
@Rob 감사합니다. 무슨 뜻인지 확인하고 코드를 수정했습니다
John Stephen

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이 설명을 나타내는 방식을 변경하면 어떻게 될까요?
Brenden

1
iOS13에서 설명 방법은 다른 형식을 반환합니다.
nacho4d

1

언제든지 [yourString uppercaseString]을 사용하여 데이터 설명에서 문자를 대문자로 표시 할 수 있습니다.


1

NSData를 NSString으로 직렬화 / 역 직렬화하는 더 좋은 방법 은 Mac Base64 인코더 / 디코더 용 Google 도구 상자 를 사용하는 것 입니다. 패키지 Foundation에서 GTMBase64.m, GTMBase64.he GTMDefines.h 파일을 앱 프로젝트로 드래그하고 다음과 같은 작업을 수행하십시오.

/**
 * 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 2011 년

1
Mac OS X 10.6 / iOS 4.0부터 NSData는 Base-64 인코딩을 수행합니다. string = [data base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]
jrc 2015 년

@jrc는 사실이지만 실제 작업 문자열을 Base-64로 인코딩하는 것을 고려하십시오. GTMBase64 # webSafeEncodeData에서와 같이 iOS / MacOS에는없는 "웹 안전"인코딩보다 처리해야합니다. 또한 Base64 "패딩"을 추가 / 제거해야 할 수 있으므로이 옵션도 있습니다. GTMBase64 # stringByWebSafeEncodingData : (NSData *) data padded : (BOOL) padded;
loretoparisi

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

변경 %08x하는 %08X자본 문자를받을 수 있습니다.


6
컨텍스트를 포함하지 않았으므로 이것은 주석으로 더 좋을 것입니다. Just sayin '
Brenden

0

스위프트 + 속성.

속성으로 16 진수 표현을 선호합니다 ( bytesdescription속성 과 동일 ).

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.