My Favorite Macros for Objective-C Development in Xcode

4 minute read

Some developers say that macros should be avoided, but I find them very useful to add to my Xcode projects. By simply importing a single file -- UAMacros.h -- I immediately get the benefits of using these macro shortcuts to make my code much more readable. I'll admit that a negative point to using this approach is that sharing code without the macro file is not possible. Usually, if a Macro is used exclusively in one file, I will include it in the .h file only. These macros I am showing you below are usually pretty useful across your entire codebase.

Timer Invalidation

#define UA_invalidateTimer(t) [t invalidate]; t = nil;

This is pretty self explanatory... it simply invalidates and nils the timer in one line. you call it like UA_invalidateTimer(self.timer);

Device Info

#define UA_isIPad (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
#define UA_isIPhone (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)

These macros allow me to shorten the if statement and make it more readable for code blocks that are device dependent: if (UA_isIPhone) ...

#define UA_isRetinaDevice ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] && [[UIScreen mainScreen] scale] >= 2)
#define UA_isMultiTaskingSupported ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)] && [[UIDevice currentDevice] isMultitaskingSupported])

Same as above, these can be used in if blocks to make it more readable. You can extrapolate and add any UA_isXXXXXXSupported by creating a new line based on the format of

  1. Does x respond to the selector?
  2. Call selector

Threading

#define UA_runOnMainThread if (![NSThread isMainThread]) { dispatch_sync(dispatch_get_main_queue(), ^{ [self performSelector:_cmd]; }); return; };

This is an interesting one. Many completion handlers for network callbacks and other long running processes are run on background threads. For the most part, when I see examples of this kind of callback being referenced online, the poster envelopes the entire callback on the main thread. Because only UI updates need to be run on the main thread, this can be problematic, especially if you are doing data work before your UI updates. If I have a situation like this, I let the callback run on the background thread, but place this macro as the first line in my UI updating methods. It simply checks to see if the current thread is main, and if it isn't, dispatch the method on the current thread. Caveat: As written, it only works on methods without arguments.

- (void)doSomethingToGetStuff {
  [self doSomethingInBackgroundWithCallback:^(NSData *stuff){
  [self processStuff:stuff];
  [self updateUI];
}];
}

- (void)updateUI {
UA_runOnMainThread
  // update UI ...
}

You will probably get a warning when you use this:

performSelector may cause a leak because its selector is unknown

To get rid of it, surround the #define line like so:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
#define UA_runOnMainThread if (![NSThread isMainThread]) { dispatch_sync(dispatch_get_main_queue(), ^{ [self performSelector:_cmd]; }); return; };
#pragma clang diagnostic pop

Colors

#define UA_rgba(r,g,b,a) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:a]
#define UA_rgb(r,g,b) UA_rgba(r, g, b, 1.0f)

These macros make it easy and shorter to specify colors using RGB values: UA_rgb(255,242,235)

Convenience

#import "UAAppDelegate.h"
#define UA_appDelegate ((UAAppDelegate *)[[UIApplication sharedApplication] delegate])

There aren't many times when you should have to access the delegate from your classes, but when you do, it should be short and readable: UA_appDelegate.window

Debugging / Logging

#ifdef DEBUG
  #define UA_log( s, ... ) NSLog( @"<%@:%d> %@", [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__,  [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#else
	#define UA_log( s, ... )
#endif

#define UA_logBounds(view) UA_log(@"%@ bounds: %@", view, NSStringFromRect([view bounds]))
#define UA_logFrame(view)  UA_log(@"%@ frame: %@", view, NSStringFromRect([view frame]))

#define NSStringFromBool(b) (b ? @"YES" : @"NO")

#define UA_SHOW_VIEW_BORDERS YES
#define UA_showDebugBorderForViewColor(view, color) if (UA_SHOW_VIEW_BORDERS) { view.layer.borderColor = color.CGColor; view.layer.borderWidth = 1.0; }
#define UA_showDebugBorderForView(view) UA_showDebugBorderForViewColor(view, [UIColor colorWithWhite:0.0 alpha:0.25])

UA_log() is used exactly like NSLog but shows additional info like the name of the file and the line number. It is a basic example of a logger that prints to the console log on debug builds and does nothing on the production builds. Writing to a log, especially if you are a verbose logger, will slow down you production app. You will also only get a chance to view thees logs on rare occasion, so there isn't much need for them. Make sure you have a PREPROCESSOR MACRO set as DEBUG=1 for all non production target build modes.

UA_logBounds and UA_logFrame can be used to quickly print the bounds and frame for any view to the console.

NSStringFromBool is useful for simple logging and should be used along the lines of NSStringFromCGPoint and NSStringFromCGRect.

UA_showDebugBorderForView is something I place in every code-created view. It allows me to set one BOOL (UA_SHOW_VIEW_BORDERS) and instantly get a border printed around the view's layer. This allows me to ensure that Auto-Layout and positioning is correct as I have intended it to be, and is especially useful on views with transparent backgrounds. You just have to make sure that UA_SHOW_VIEW_BORDERS is set to NO before deploying on the app store.

self.label = [[UILabel alloc] init];
UA_showDebugBorderForView(self.label);
[self.view addSubview:self.label];

I use many more macros than these throughout my projects and tend to add macros with these patterns whenever I find a long line that is being repeated frequently, or when a simple logging or debugging method should be used in many places, yet should be enabled with only a single boolean value. Enjoy.

Was this page helpful for you? Buy me a slice of 🍕 to say thanks!

Categories:

Updated:

Comments