Click here to Skip to main content
12,746,945 members (31,167 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as


2 bookmarked
Posted 8 Jun 2013

A Better NSData Description

, 8 Jun 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
A better NSData description


NSData has not always returned the HEX string of its data. There was a time when it behaved far more modestly (and usefully in my opinion) when it just indicated its pointer and length. Let's take advantage of the method swizzling tools we learned about in a previous post so that we can get NSData's description to be something more useful than a data dump - and we'll make it configurable too so that at runtime you can decide how NSData's description should behave.  I'll also throw in an optimal NSData-to-hex-string method for good measure, hopefully to help counteract the awful practice some developers have adopted of using the NSData description for that serialization.

Putting NSData's Description on a Diet!

So let's utilize our awesome method swizzling to be able to configure the description of NSData. The information we can show is pretty simple:

  1. The object info: the class name and the object pointer.  Ex// NSData:0xdeadbeef
  2. The data length: Ex// length=128
  3. The data in HEX: Ex// DEADBEEF 01234567

So let's define those pieces of information as an enum mask:

typedef enum
    NSDataDescriptionOption_None       = 0,            // behaves as OS default
    NSDataDescriptionOption_ObjectInfo = 1 << 0,
    NSDataDescriptionOption_Length     = 1 << 1,
    NSDataDescriptionOption_Data       = 1 << 2,
    NSDataDescriptionOption_ObjectInfoAndLength = 
    NSDataDescriptionOption_ObjectInfo | NSDataDescriptionOption_Length,
    NSDataDescriptionOption_ObjectInfoAndData   = 
    NSDataDescriptionOption_ObjectInfo | NSDataDescriptionOption_Data,
    NSDataDescriptionOption_LengthAndData       = 
    NSDataDescriptionOption_Length | NSDataDescriptionOption_Data,
    NSDataDescriptionOption_AllOptions          = 
    NSDataDescriptionOption_ObjectInfo | NSDataDescriptionOption_Length | 
} NSDataDescriptionOptions;

Now we'll declare a category specifically for configuring the description of NSData objects:

@interface NSData (Description)

+ (NSDataDescriptionOptions) descriptionOptions;
+ (NSDataDescriptionOptions) setDescriptionOptions:
	(NSDataDescriptionOptions)options; // returns the previous options


Now let's get into the swizzling:

static NSDataDescriptionOptions s_options = 
		NSDataDescriptionOption_None; // OS default

@implementation NSData (Description)

+ (NSDataDescriptionOptions) descriptionOptions
    @synchronized (self) {
        return s_options;

+ (NSDataDescriptionOptions) setDescriptionOptions:(NSDataDescriptionOptions)options
    @synchronized(self) {
        if (s_options != options)
            if (NSDataDescriptionOption_None == s_options ||
                NSDataDescriptionOption_None == options)
                // Swizzle - either to the custom description or back to the native description
                SwizzleInstanceMethods([NSData class], 
                @selector(description), @selector(_configuredDescription));

            NSDataDescriptionOptions tmp = s_options;
            s_options = options;
            options = tmp;
        return options;

#pragma mark - Internal

- (NSString*) _configuredDescription
    NSDataDescriptionOptions options = s_options;
    NSMutableString* dsc = [NSMutableString string];
    [dsc appendString:@"<"];

    if (NSDataDescriptionOption_ObjectInfo & options)
        [dsc appendFormat:@"%@:%p", NSStringFromClass([self class]), self];

    if (NSDataDescriptionOption_Length & options)
        if (dsc.length > 1)
            [dsc appendString:@" "];
        [dsc appendFormat:@"length=%d", self.length];

    if (NSDataDescriptionOption_Data & options)
        if (dsc.length > 1)
            [dsc appendString:@" "];
        [dsc appendString:[self hexStringValueWithDelimter:@" " 
        	everyNBytes:4]]; // hexStringValue will be shown at 
        			// the end of this post as a "bonus"

    [dsc appendString:@">"];
    return dsc;  // you could return [[dsc copy] autorelease], but it's not a big issue 
    	// if someone mutates the return string and copying a potentially large string 
    	// (like when the Data option is set) is memory consuming


We'll use a static variable to keep track of what configuration has been set. When the configuration is None the description method will be the native implementation, otherwise it's our custom _configuredDescription method.

We'll synchronize on both getting and setting the description options, but as an added tool for synchronization, we'll have the setDescriptionOptions: class method return the previous NSDataDescriptionOptions.

For the setDescriptionOptions: we take advantage of our method swizzling, knowing that if we are transitioning from the native description to a custom description, we need to swizzle and if we are transitioning from our custom description to the native one, we just swizzle back. So simple! We also swap the static options with the provided options so that we can return the options variable as the previously used description options.

Implementing the actual new description is also straightforward, it's just a matter of adding all the matching pieces of information to the description in a priority order and having the pieces be separated by a space and surrounded in triangle braces.

I did put in some handwaving with the hexStringValueWithDelimeter:everyNBytes: method. Now, remembering that the description method for NSData has changed in the past and could change in the future, it is better to not presume that the description is a HEX string and that's why we will implement our own NSData to HEX string method and use that.

@interface NSData (Serialize)

- (NSString*) hexStringValue;
- (NSString*) hexStringValueWithDelimeter:(NSString*)delim everyNBytes:(NSUInteger)nBytes;


// can change the base char to be 'a' for lowercase hex strings

NS_INLINE void byteToHexComponents(unsigned char byte, unichar* pBig, unichar* pLil)
    assert(pBig && pLil);
    unsigned char c = byte / 16;
    if (c < 10)
        c += '0';
        c += HEX_ALPHA_BASE_CHAR - 10;
    *pBig = c;
    c     = byte % 16;
    if (c < 10)
        c += '0';
        c += HEX_ALPHA_BASE_CHAR - 10;
    *pLil = c;

@implementation NSData (Serialize)

- (NSString*) hexStringValue
    return [self hexStringValueWithDelimeter:nil everyNBytes:0]; // no delimiter

- (NSString*) hexStringValueWithDelimeter:(NSString*)delim everyNBytes:(NSUInteger)nBytes
    NSUInteger     len       = self.length;
    NSUInteger     newLength = 0;
    BOOL           doDelim   = nBytes > 0 && delim.length;
    if (doDelim)
         newLength = (len / nBytes) * delim.length;
         if ((len % nBytes) == 0 && newLength > 0)
             newLength -= delim.length;
    newLength += len*2; // each byte turns into 2 HEX chars
    unichar*       hexChars    = (unichar*)malloc(sizeof(unichar) * newLength);
    unichar*       hexCharsPtr = hexChars;
    unsigned char* bytes       = (unsigned char*)self.bytes;

    // By pulling out the implementation of getCharacters:range: for reuse, 
    // we optimize out the ObjC class hierarchy traversal 
    // for the implementation while in our loop
    SEL            getCharsSel = @selector(getCharacters:range:);
    IMP            getCharsImp = [delim methodForSelector:getCharsSel];
    NSRange        getCharsRng = NSMakeRange(0, delim.length);
    for (NSUInteger i = 0; i < len; i++)
        if (doDelim && (i > 0) && (i % nBytes == 0))
            getCharsImp(delim, getCharsSel, hexCharsPtr, getCharsRng);
            hexCharsPtr += getCharsRng.length;
        byteToHexComponents(bytes[i], hexCharsPtr++, hexCharsPtr++);
    assert(hexCharsPtr - newLength == hexChars);
    return [[[NSString alloc] initWithCharactersNoCopy:hexChars
                                          freeWhenDone:YES] autorelease];


Find this code on github.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Technical Lead
United States United States
No Biography provided

You may also be interested in...


Comments and Discussions

-- There are no messages in this forum --
Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170215.1 | Last Updated 8 Jun 2013
Article Copyright 2013 by NSProgrammer
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid