Cách tốt nhất để tuần tự hóa một NSData thành một chuỗi gần thập lục phân


101

Tôi đang tìm một cách hay ho để tuần tự hóa một đối tượng NSData thành một chuỗi thập lục phân. Ý tưởng là tuần tự hóa deviceToken được sử dụng để thông báo trước khi gửi nó đến máy chủ của tôi.

Tôi có cách triển khai sau đây, nhưng tôi đang nghĩ phải có một số cách ngắn hơn và đẹp hơn để thực hiện nó.

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

Câu trả lời:


206

Đây là một danh mục áp dụng cho NSData mà tôi đã viết. Nó trả về một Chuỗi NSSt thập lục phân đại diện cho NSData, nơi dữ liệu có thể có độ dài bất kỳ. Trả về một chuỗi trống nếu NSData trống.

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

Sử dụng:

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

Điều này "có lẽ" tốt hơn là gọi [someData description]và sau đó loại bỏ các dấu cách, <'s và>. Nhân vật lột đồ chỉ cảm thấy quá "hacky". Thêm vào đó, bạn không bao giờ biết liệu Apple có thay đổi định dạng của NSData -descriptiontrong tương lai hay không.

LƯU Ý: Tôi đã có người liên hệ với tôi về việc cấp phép cho mã trong câu trả lời này. Bằng cách này, tôi dành bản quyền của mình trong mã mà tôi đã đăng trong câu trả lời này cho miền công cộng.


4
Tốt, nhưng có hai đề xuất: (1) Tôi nghĩ appendFormat hiệu quả hơn cho dữ liệu lớn vì nó tránh tạo ra một NSString trung gian và (2)% x đại diện cho một int không dấu thay vì không dấu dài, mặc dù sự khác biệt là vô hại.
svachalek

Không phải là một người phản đối, vì đây là một giải pháp tốt và dễ sử dụng nhưng giải pháp của tôi vào ngày 25 tháng 1 hiệu quả hơn nhiều. Nếu bạn đang tìm kiếm câu trả lời được tối ưu hóa hiệu suất, hãy xem câu trả lời đó . Ủng hộ câu trả lời này như một giải pháp hay, dễ hiểu.
NSProgrammer

5
Tôi đã phải xóa diễn viên (dài không dấu) và sử dụng @ "% 02hhx" làm chuỗi định dạng để làm cho điều này hoạt động.
Anton

1
Đúng vậy, mỗi developer.apple.com/library/ios/documentation/cocoa/conceptual/... định dạng nên "%02lx"với dàn diễn viên đó, hoặc cast để (unsigned int), hoặc thả các diễn viên và sử dụng @"%02hhx":)
qix

1
[hexString appendFormat:@"%02x", (unsigned int)dataBuffer[i]];là tốt hơn nhiều (bộ nhớ dấu chân nhỏ hơn)
Marek R

31

Đây là một phương pháp danh mục NSData được tối ưu hóa cao để tạo một chuỗi hex. Mặc dù câu trả lời của @Dave Gallagher là đủ cho kích thước tương đối nhỏ, nhưng hiệu suất bộ nhớ và cpu sẽ kém đi đối với lượng lớn dữ liệu. Tôi đã lập hồ sơ này bằng một tệp 2MB trên iPhone 5. So sánh thời gian là 0,05 so với 12 giây. Dấu chân bộ nhớ là không đáng kể với phương pháp này trong khi phương pháp khác đã tăng bộ nhớ lên 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 @"";
}

Nice one @ Peter - Có tuy nhiên là một thậm chí nhanh hơn (không nhiều so với của bạn) giải pháp - chỉ dưới đây;)
Moose

1
@Moose, vui lòng tham khảo chính xác hơn câu trả lời bạn đang nói: phiếu bầu và câu trả lời mới có thể ảnh hưởng đến việc định vị câu trả lời. [chỉnh sửa: ồ, để tôi đoán, ý bạn là câu trả lời của riêng bạn ...]
Cœur

1
Đã thêm kiểm tra null malloc. Cảm ơn @ Cœur.
Peter

17

Việc sử dụng thuộc tính mô tả của NSData không nên được coi là một cơ chế được chấp nhận để mã hóa chuỗi HEX. Thuộc tính đó chỉ để mô tả và có thể thay đổi bất kỳ lúc nào. Như một lưu ý, trước iOS, thuộc tính mô tả NSData thậm chí không trả về dữ liệu của nó ở dạng hex.

Xin lỗi vì đã làm hỏng giải pháp này nhưng điều quan trọng là phải dành sức lực để tuần tự hóa nó mà không cần hỗ trợ một API dành cho một thứ khác ngoài tuần tự hóa dữ liệu.

@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

tuy nhiên, bạn phải giải phóng (hexChars) trước khi trả lại.
karim

3
@karim, điều đó không chính xác. Bằng cách sử dụng initWithCharactersNoCopy: length: freeWhenDone: và có freeWhenDone be YES, NSString sẽ kiểm soát bộ đệm byte đó. Gọi miễn phí (hexChars) sẽ dẫn đến sự cố. Lợi ích ở đây là đáng kể vì NSString sẽ không phải thực hiện một cuộc gọi memcpy đắt tiền.
NSProgrammer

@NSProgrammer cảm ơn. Tôi không nhận thấy trình khởi tạo NSSting.
karim

Tài liệu nói rằng descriptiontrả về một chuỗi được mã hóa hex, vì vậy điều đó có vẻ hợp lý với tôi.
Không phổ biến

chúng ta không nên kiểm tra giá trị trả về malloc có khả năng là null không?
Cœur

9

Đây là một cách nhanh hơn để thực hiện chuyển đổi:

BenchMark (thời gian trung bình cho một chuyển đổi dữ liệu 1024 byte lặp lại 100 lần):

Dave Gallagher: ~ 8.070 ms
NSProgrammer: ~ 0.077 ms
Peter: ~ 0.031 ms
Cái này: ~ 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
Bạn có thể xem cách tôi triển khai kiểm tra malloc tại đây ( _hexStringphương pháp): github.com/ZipArchive/ZipArchive/blob/master/SSZipArchive/…
Cœur

Cảm ơn bạn đã tham khảo - BTW Tôi thích 'quá dài dòng' - Đúng vậy, nhưng bây giờ tôi đã gõ nó, bất kỳ ai cũng có thể sao chép / dán - Tôi nói đùa - Tôi đã tạo ra nó - Bạn biết rồi đấy :) Bạn đã đúng dài dòng, tôi chỉ đang cố gắng để đạt được mọi nơi mà tôi có thể giành được micro giây! Nó chia các lần lặp vòng lặp cho 2. Nhưng tôi thừa nhận nó thiếu sự sang trọng. Tạm biệt
Moose

8

Phiên bản Swift chức năng

Lót:

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

Đây là một dạng tiện ích mở rộng có thể tái sử dụng và tự ghi lại:

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

Ngoài ra, hãy sử dụng reduce("", combine: +)thay vì joinWithSeparator("")được đồng nghiệp của bạn coi là bậc thầy chức năng.


Chỉnh sửa: Tôi đã thay đổi Chuỗi ($ 0, cơ số: 16) thành Chuỗi (định dạng: "% 02x", $ 0), bởi vì các số có một chữ số cần có phần đệm bằng 0


7

Câu trả lời của Peter được chuyển sang 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 "";
}

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

Tôi cần giải quyết vấn đề này và thấy các câu trả lời ở đây rất hữu ích, nhưng tôi lo lắng về hiệu suất. Hầu hết các câu trả lời này liên quan đến việc sao chép dữ liệu hàng loạt ra khỏi NSData, vì vậy tôi đã viết phần sau để thực hiện chuyển đổi với chi phí thấp:

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

Điều này phân bổ trước không gian trong chuỗi cho toàn bộ kết quả và tránh sao chép nội dung NSData ra ngoài bằng cách sử dụng enumerateByteRangesUsingBlock. Thay đổi X thành x trong chuỗi định dạng sẽ sử dụng các chữ số hex viết thường. Nếu bạn không muốn có dấu phân cách giữa các byte, bạn có thể giảm câu lệnh

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

xuống chỉ

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

2
Tôi tin rằng chỉ mục để truy xuất giá trị byte cần điều chỉnh, bởi vì NSRangechỉ số cho biết phạm vi trong phần NSDatabiểu diễn lớn hơn , không nằm trong bộ đệm byte nhỏ hơn (tham số đầu tiên của khối được cung cấp cho enumerateByteRangesUsingBlock) đại diện cho một phần liền kề duy nhất của khối lớn hơn NSData. Do đó, byteRange.lengthphản ánh kích thước của bộ đệm byte, nhưng byteRange.locationlà vị trí bên trong lớn hơn NSData. Vì vậy, bạn chỉ muốn sử dụng đơn giản offset, không phải byteRange.location + offset, để lấy byte.
Rob

1
@Rob Cảm ơn, tôi hiểu ý bạn và đã điều chỉnh mã
John Stephen

1
Nếu bạn sửa đổi tuyên bố xuống chỉ sử dụng duy nhất appendFormatbạn nên có lẽ cũng thay đổi self.length * 3đểself.length * 2
T. Colligan

1

Tôi cần một câu trả lời phù hợp với các chuỗi có độ dài thay đổi, vì vậy đây là những gì tôi đã làm:

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

Hoạt động tuyệt vời như một phần mở rộng cho lớp NSString.


1
điều gì sẽ xảy ra nếu Apple thay đổi cách họ thể hiện mô tả?
Brenden

1
trong phương thức mô tả iOS13 trả về một định dạng khác.
nacho4d

1

Bạn luôn có thể sử dụng [yourString uppercaseString] để viết hoa các chữ cái trong mô tả dữ liệu


1

Cách tốt hơn để tuần tự hóa / giải mã hóa NSData thành NSString là sử dụng bộ mã hóa / giải mã Base64 của Hộp công cụ Google dành cho Mac . Chỉ cần kéo vào Dự án ứng dụng của bạn các tệp GTMBase64.m, GTMBase64.he GTMDefines.h từ gói Foundation và làm điều gì đó như

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

}

Nhìn vào mã nguồn , có vẻ như lớp cung cấp hiện là GTMStringEncoding. Tôi đã không thử nó nhưng nó có vẻ như một giải pháp mới tuyệt vời cho câu hỏi này.
sarfata

1
Kể từ Mac OS X 10.6 / iOS 4.0, NSData thực hiện mã hóa Base-64. string = [data base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]
jrc

@jrc điều đó đúng, nhưng hãy xem xét để mã hóa chuỗi công việc thực trong Base-64. Bạn phải đối phó với mã hóa "web an toàn" mà bạn không có trong iOS / MacOS, như trong GTMBase64 # webSafeEncodeData. Ngoài ra, bạn có thể cần thêm / xóa "padding" Base64, vì vậy bạn cũng có tùy chọn này: GTMBase64 # stringByWebSafeEncodingData: (NSData *) data padded: (BOOL) padded;
loretoparisi

1

Đây là một giải pháp sử dụng 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

Thay đổi %08xthành %08Xđể có các ký tự viết hoa.


6
điều này sẽ tốt hơn dưới dạng một bình luận vì bạn không bao gồm bất kỳ ngữ cảnh nào. Just sayin '
Brenden

0

Swift + Thuộc tính.

Tôi muốn có biểu diễn hex làm thuộc tính (giống bytesdescriptionthuộc tính):

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

Ý tưởng được mượn từ câu trả lời này


-4
[deviceToken description]

Bạn sẽ cần xóa khoảng trắng.

Cá nhân tôi base64mã hóa deviceToken, nhưng đó là vấn đề của thị hiếu.


Điều này không nhận được cùng một kết quả. lợi nhuận mô tả: <2cf56d5d 2fab0a47 ... 7738ce77 7e791759> Trong khi tôi đang tìm kiếm: 2CF56D5D2FAB0A47 .... 7738CE777E791759
sarfata
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.