Published on

Empowered with Macros

Authors

In the C, C++, and Objective-C communities, many people talk about how terrible macros are. I think macros can be dangerous but I also think they're misunderstood. In some cases (I will show you one in the end) macros can save you lots of time and effort.

What Are Macros?

To better understand what macros are, let's play with the Objective-C preprocessor. Xcode allows us to easily play with the preprocessor by going to the Menu bar and going to:

Product > Perform Action > Preprocess "main.m"

Demo

In this example, you'll see a string taking place of a macro, and the macro definition being removed. This is all done by the preprocessor to optimize your code. Writing a basic Objective-C macro looks like this:

#define macro_name macro_value

// main.m (before preprocessing)
#import <Foundation/Foundation.h>

// our macro
#define demo @"Hello, World!"

int main(int argc, const char * argv[]) {
	@autoreleasepool {
        // using the macro
		NSLog(demo);
	}
    return 0;
}
// main.m (after preprocessing)
@import Foundation;

int main(int argc, const char * argv[]) {
	@autoreleasepool {
		NSLog(@"Hello, World!");
	}
	return 0;
}

Output:

Hello, World!

The demo above can easily be done by a const value. This use of macros is not recommended, but it is a good example of what macros do.

Parameterized Macros

As you can imagine the uses of macros extend far beyond a constant value, otherwise I wouldn't be writing this! Another use of macros is for functions which can take parameters and return a value.

A popular example of a macro that does this converts a hex value to a UIColor value.

#define UIColorFromHex(hexValue) \
	[UIColor colorWithRed:((float)((hexValue & 0xFF0000) >> 16))/255.0 \
	green:((float)((hexValue & 0xFF00) >> 8))/255.0 \
	blue:((float)(hexValue & 0xFF))/255.0 \
	alpha:1.0]

Usage

UIColorFromHex(0x000000);

// gets expanded to
[UIColor colorWithRed:((float)((0x000000 & 0xFF0000) >> 16))/255.0 green:((float)((0x000000 & 0xFF00) >> 8))/255.0 blue:((float)(0x000000 & 0xFF))/255.0 alpha:1.0];

As you can see, the macro is defined with one parameter (hexValue). Notice how the type is omitted in macro definitions. This means that macros are not type safe. This makes macros rather prone to crashes and is one of the reasons they have a bad reputation.

If I were to put a value other than a hex value inside of the macro, it's almost guaranteed to crash, so when using a macro to expand code, be 100% certain no other value will find its way into the arguments.

Method Definitions

In my previous blog post, I wrote about Templates in Xcode. This trick could easily be a part of that previous post as it involves some form of templating. In this case, templating is used to define method names.

The best example I've seen of method definitions generated from macros comes from twotoasters/Toast. In CAMediaTimingFunction+TWTEasingFunctions.m they define a timing function using a macro and then use that macro to define many other timing functions that only differ in a few values.

// macro definition
#define TWTMediaTimingFunctionMethodWithControlPoints(name, c1x, c1y, c2x, c2y)  \
+ (instancetype)twt_##name##Function                                             \
{                                                                                \
    static CAMediaTimingFunction *function = nil;                                \
    static dispatch_once_t onceToken;                                            \
    dispatch_once(&onceToken, ^{                                                 \
        function = [[self alloc] initWithControlPoints:c1x :c1y :c2x :c2y];      \
    });                                                                          \
    return function;                                                             \
}

// sineEase functions (before preprocessing)
TWTMediaTimingFunctionMethodWithControlPoints(sineEaseIn, 0.47, 0, 0.745, 0.715);
TWTMediaTimingFunctionMethodWithControlPoints(sineEaseOut, 0.39, 0.575, 0.565, 1);
TWTMediaTimingFunctionMethodWithControlPoints(sineEaseInOut, 0.445, 0.05, 0.55, 0.95);

...
// sineEase functions (after preprocessing)
+ (instancetype)twt_sineEaseInFunction {
	static CAMediaTimingFunction *function = ((void *)0);
	static dispatch_once_t onceToken;
	_dispatch_once(&onceToken, ^{
		function = [[self alloc] initWithControlPoints:0.47 :0 :0.745 :0.715];
	});
	return function;
};

+ (instancetype)twt_sineEaseOutFunction {
	static CAMediaTimingFunction *function = ((void *)0);
	static dispatch_once_t onceToken;
	_dispatch_once(&onceToken, ^{
		function = [[self alloc] initWithControlPoints:0.39 :0.575 :0.565 :1];
	});
	return function;
};

+ (instancetype)twt_sineEaseInOutFunction {
	static CAMediaTimingFunction *function = ((void *)0);
	static dispatch_once_t onceToken;
	_dispatch_once(&onceToken, ^{
		function = [[self alloc] initWithControlPoints:0.445 :0.05 :0.55 :0.95];
	});
	return function;
};

...

This macro allowed them to write ~250 LOC with only ~75 LOC. Pretty neat! You can view the entire postprocessed file here if you'd like.