Handy NSString Conversion Macro
Some of the most useful functions of Foundation that are not often well known are the functions to convert from non-objects to NSString
:
NSStringFromRect()
, NSStringFromRange()
, NSStringFromClass()
, etc…
For example, to print out a the bounds of a view:
NSLog(@"Bounds: %@", NSStringFromRect([view bounds]));
For some reason, I always have trouble remembering the names of these functions. Sometimes I think NSStringWithRect
, sometimes I think NSRectToString
, plus, I have to remember if it’s a rectangle, point, or size.
In order to simplify this and let my brain worry about more important details, I created a single macro to replace all these functions: DDToNSString
:
NSLog(@"Bounds: %@", DDToNSString([view bounds]));
This works for NSPoint
, NSSize
, NSRect
, NSRange
, Class
, SEL
(selectors), and BOOL
(using DDNSStringFromBOOL
).
If you’re curious how it works, read on. If you just want to use it, it’s part of DDFoundation version 1.0b2 1.0b3.
If I wanted to require Objective-C++ I could just use an overloaded function. But I want this to work in plain Objective-C as well as Objective-C++. The only way to do this in essentially straight C is with a macro and some rather obscure GCC and Objective-C syntax. Here’s the declaration:
NSString * DDToStringFromTypeAndValue(const char * typeCode, void * value);
#define DDToNString(_X_) ({typeof(_X_) _Y_ = (_X_);\
DDToStringFromTypeAndValue(@encode(typeof(_X_)), &_Y_);})
The macro is using Objective-C type encoding (the @encode
keyword) to convert types into C strings. For example @encode(NSRect)
returns:
{_NSRect={_NSPoint=ff}{_NSSize=ff}}
The @encode
operator takes a type as its argument. We can use the GCC typeof
operator to get the type of an expression, hence the @encode(typeof(_X_))
.
The function that does the actual work uses this encoded type string to call the correct Foundation function. Along with the type information, the macro passes a pointer to the value we want to convert. We have to use a pointer because we don’t know the type at compile to use pass by value. The void *
will let us pass any pointer.
Finally, we want to be able to use this on return values, like our [view bounds]
example:
NSLog(@"Bounds: %@", NSStringFromRect([view bounds]));
My original implementation of the macro was this simpler version:
#define DDToNString(_X_) \
DDToStringFromTypeAndValue(@encode(typeof(_X_)), &(_X_))
This will not work with a return value because &(_X_)
expands to &([view bounds])
. You cannot take the address of a return value like that, only a variable. Thus, you’d have to put the result in a temporary variable:
NSRect bounds = [view bounds];
NSLog(@"Bounds: %@", NSStringFromRect(bounds));
The way to get around this problem is use another GCC extension allowing statements in expressions. Thus, the macro creates a temporary variable, _Y_
, with the same type of _X_
(again using typeof
) and passes the address of this temporary to the function. That’s how I ended up at:
#define DDToNString(_X_) ({typeof(_X_) _Y_ = (_X_);\
DDToStringFromTypeAndValue(@encode(typeof(_X_)), &_Y_);})
The implementation of DDToStringFromTypeAndValue
is pretty straightforward:
NSString * DDToStringFromTypeAndValue(const char * typeCode, void * value)
{
if (strcmp(typeCode, @encode(NSPoint)) == 0)
{
return NSStringFromPoint(*(NSPoint *)value);
}
else if (strcmp(typeCode, @encode(NSSize)) == 0)
{
return NSStringFromSize(*(NSSize *)value);
}
// else if for other types...
}
This could probably be made more efficient by using bsearch(3) or a C++ map
. But in the interested of keeping it simple and not optimizing until necessary, I think this is fine. I usually only use this feature for debug logging, anyways.