iOS Development: A Fistful of Directive Tricks
Converting Comments to Warnings
I first encountered this clever Build Phase trick about a year ago. It enables developers to convert common to-do items prefixes to automatically built compiler warnings via regular expression matching. When you build your app, a shell script matches any occurrence of TODO:, FIXME:, ???, or ///, automatically inserting a #warning directive so that these items show up as compile-time issues, as shown in Figure 1.
Figure 1 A build phase script converts comments to warnings
The great advantage of this is that it automatically centralizes project status items to your Xcode issue navigator, where you can inspect an overview throughout your project. Warnings and errors are listed on a file-by-file basis, so you can keep track of open issues you still need to address, as shown in Figure 2.
Figure 2 The issue navigator collects the converted warnings
It takes just a few steps to implement this effect.
- Customize your project by adding a build phase. Select Editor > Add Build Phase > Add Run Script Build Phase.
- Open the Run Script section and locate the section that says “Type a script or drag a script file from your workspace to insert its path,” as shown in Figure 3.
Figure 3 A run script build phase enables you to script changes to your build
- Paste in the following script. If you want, edit the |-delimited keywords list to customize it to your particular documentation needs.
- Compile. Your //TODO:, //FIXME:, //!!!:, and //???: comments automatically upgrade to warnings, enabling you to track to-do items throughout your project from a single navigator.
KEYWORDS="TODO:|FIXME:|\?\?\?:|\!\!\!:" find "${SRCROOT}" \( -name "*.h" -or -name "*.m" \) -print0 | xargs -0 egrep --with-filename --line-number --only- matching "($KEYWORDS).*\$" | perl -p -e "s/($KEYWORDS)/ warning: \$1/"
Warnings
If you’d rather not tie yourself to specific comment patterns like the ones you just read about, consider using the Clang #warning directive directly. The following sequence tests for an iOS compilation target and generates a warning accordingly, as shown in Figure 4.
#if TARGET_OS_IPHONE #warning This class is not meant for iOS deployment #endif
Figure 4 The #warning directive produces a compiler warning. When working with deployment mismatches, you may want to escalate this to an error
This next example uses a target test, too. I’ve been writing a lot of cross-platform code recently for constraint utilities. Because TARGET_OS_MAC returns true for both iOS and OS X targets, you’ll always want to test for TARGET_OS_IPHONE instead.
#ifndef COMPATIBILITY_ALIASES_DEFINED #if TARGET_OS_IPHONE @compatibility_alias View UIView; @compatibility_alias Color UIColor; #else @compatibility_alias View NSView; @compatibility_alias Color NSColor; #endif #endif #define COMPATIBILITY_ALIASES_DEFINED
The @compatibility_alias keyword used in this example enables you to map an alias (like View or Color to platform-specific classes (like UIView or NSView, or UIColor or NSColor). Guarding this code with the definition declaration ensures that these aliases are defined just once in the project, avoiding compilation errors.
Errors
The #error directive works in a similar fashion to the #warning one. The following example checks that a required include file was added in the project. It produces a compiler error if it’s missing.
#if !__has_include("Utility-Compatibility.h") #error ERROR! Required Cross Compatibility include file not found! #endif
On compile, the error produces a red-circled exclamation point error feedback instead of the yellow triangle, as shown in Figure 5. Use this directive when you detect inconsistent compilation conditions (for example, missing include files) or unsupported compiler targets (for example, when you know a class will not compile properly for iOS or OS X).
Figure 5 Error directives produce a red-colored fatal error
Messages
Message pragmas are directives that, as their name suggests, produce messages. They won’t convert to an error regardless of compiler settings. Here’s an example of a message that reminds you about some to-do item.
#pragma message ("To Do: Extend this to include Feature A")
Like #warning and #error directives, messages show up in your issues navigator and with inline highlighting, as shown in Figure 6. However, they are informational only. The compiler basically ignores them, even when you’ve switched on “Treat Errors as Warnings.”
Figure 6 The Issue navigator aggregates compilation warnings and errors
Wrapping Pragmas
Diagnostic messages can themselves be wrapped into macros to produce standardized output. This is a handy trick, one that enables you to wrap message requests into reusable components. Here are a couple macros that combine to create To Do items.
#define DO_PRAGMA(_ARG_) _Pragma (#_ARG_) #define TODO(_ITEM_) DO_PRAGMA(message ("To Do: " #_ITEM_))
When you invoke the TODO macro, it builds the same results as the message directive you just saw. It supplies a message argument to the _Pragma operator and adds the "To Do: " prefix into the message it builds.
TODO(Extend this to include Feature A)
If you want, you can incorporate standard compiler-supplied macros like __FILE__, __LINE__, __DATE__, and __TIME__ with message directives. For example, you might add the following message (see Figure 7), which uses the __FILE__ macro to an implementation file.
#pragma message "Compiling" __FILE__
Figure 7 Compiler-supplied macros provide information about the items being processed
Overriding Diagnostics
You override diagnostics with the diagnostic ignored pragma. The following example temporarily disables warnings about undeclared selectors and their leaks. By surrounding these items with push and pop directives, you localize those overrides to just this section. The compiler continues to tag undeclared selector usage in the rest of your project.
#pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" #pragma clang diagnostic ignored "-Warc-performSelector-leaks" void SafePerform(id target, SEL selector, NSObject *object) { if ([target respondsToSelector:selector]) [target performSelector:selector withObject:object]; } #pragma clang diagnostic pop
Unused Variable Warnings
Unused variable warnings often crop up in development. They provide a good example of how you can solve a problem using any number of directives (see Figure 8).
Figure 8 Compiler-supplied macros provide information about the items being processed
As with selector issues, you can use a “diagnostic ignored” pragma like the following to suppress the compiler warning.
#pragma clang diagnostic ignored "-Wunused-variable"
Or you can mark the variable with an attribute.
NSString *description __attribute__ ((unused));
Or use the __unused keyword.
NSInteger __unused index;
There’s also a specific unused pragma available:
#pragma unused (description)
You can also wrap this pragma into its own macro if you’re so inclined.
// Create "unused" macro #define UNUSED(_ITEM_) DO_PRAGMA(unused(_ITEM_)) // Use the macro in-line with the declaration NSString *description; UNUSED(description);
Wrap-Up
This little overview presented a number of ways you can add or suppress diagnostic messages in Xcode projects. These represent just a subset of the range of compiler customizations you can use to take charge of your projects. Whether you’re enhancing visibility of your workflow To Do items or hiding warnings for items that don’t actually concern you, Xcode and Clang provide you with sophisticated tools that adapt to your individual development style.