Effective Objective-C 2.0: Item 4: Prefer Typed Constants to Preprocessor #define

原创 2013年12月02日 11:35:15

Item 4: Prefer Typed Constants to Preprocessor #define

曾经面试被被问到 Preprocessor #define 有什么缺点, 这篇文章很好解答了这个问题。 ^ ^

When writing code, you will often want to define a constant. For example, consider a UI view class that presents and dismisses itself using animations. A typical constant that you’d likely want to factor out is the animation duration. You’ve learned all about Objective-C and its C foundations, and so you take the approach of defining the constant like this:


This is a preprocessor directive; whenever the stringANIMATION_DURATION is found in your source code, it is replaced with 0.3. This might seem exactly what you want, but this definition has no type information. It is likely that something declared as a “duration” means that the value is related to time, but it’s not made explicit.Also, the preprocessor will blindly replace all occurrences ofANIMATION_DURATION, so if that were declared in a header file, anything else that imported that header would see the replacement done.

To solve these problems, you should make use of the compiler. There is always a better way to define a constant than using a preprocessor define. For example, the following defines a constant of typeNSTimeInterval:

static const NSTimeInterval kAnimationDuration = 0.3;

Note that with this style, there is type information, which is beneficial because it clearly defines what the constant is. The type isNSTimeInterval, and so it helps to document the use of that variable. If you have a lot of constants to define, this will certainly help you and other people who read the code later.

Also note how the constant is named. The usual convention for constants is to prefix with the letter k for constants that are local to a translation unit (implementation file). For constants that are exposed outside of a class, it is usual to prefix with the class name. Item 19 explains more about naming conventions.

It is important where you define your constants. Sometimes, it is tempting to declare preprocessor defines in header files, but that is extremely bad practice, especially if the defines are not named in such a way that they won’t clash. For example, the ANIMATION_DURATIONconstant would be a bad name to appear in a header file. It would be present in all other files that imported the header. Even the static const as it stands should not appear in a header file. Since Objective-C has no namespaces, it would declare a global variable calledkAnimationDuration. Its name should be prefixed with something that scopes it to the class it is to be used with, such asEOCViewClassAnimationDuration. Item 19 explains more about using a clear naming scheme.

A constant that does not need to be exposed to the outside world should be defined in the implementation file where it is used. For example, if the animation duration constant were used in a UIViewsubclass, for use in an iOS application that uses UIKit, it would look like this:

// EOCAnimatedView.h
#import <UIKit/UIKit.h>

@interface EOCAnimatedView : UIView
- (void)animate;

// EOCAnimatedView.m
#import "EOCAnimatedView.h"

static const NSTimeInterval kAnimationDuration = 0.3;

@implementation EOCAnimatedView
- (void)animate {
    [UIView animateWithDuration:kAnimationDuration
                         // Perform animations

It is important that the variable is declared as both static andconst. The const qualifier means that the compiler will throw an error if you try to alter the value. In this scenario, that’s exactly what is required. The value shouldn’t be allowed to change. The staticqualifier means that the variable is local to the translation unit in which it is defined. A translation unit is the input the compiler receives to generate one object file. In the case of Objective-C, this usually means that there is one translation unit per class: every implementation (.m) file. So in the preceding example,kAnimationDuration will be declared locally to the object file generated from EOCAnimatedView.m. If the variable were not declaredstatic, the compiler would create an external symbol for it. If another translation unit also declared a variable with the same name, the linker would throw an error with a message similar to this:

duplicate symbol _kAnimationDuration in:

In fact, when declaring the variable as both static and const, the compiler doesn’t end up creating a symbol at all but instead replaces occurrences just like a preprocessor define does. 

Remember, however, the benefit is that the type information is present.

Sometimes, you will want to expose a constant externally. For example, you might want to do this if your class will notify others using NSNotificationCenter. This works by one object posting notifications and others registering to receive them. Notifications have a string name, and this is what you might want to declare as an externally visible constant variable. Doing so means that anyone wanting to register to receive such notifications does not need to know the actual string name but can simply use the constant variable.

Such constants need to appear in the global symbol table to be used from outside the translation unit in which they are defined. Therefore, these constants need to be declared in a different way from thestatic const example. These constants should be defined like so:

// In the header file
extern NSString *const EOCStringConstant;

// In the implementation file
NSString *const EOCStringConstant = @"VALUE";

The constant is “declared” in the header file and “defined” in the implementation file. In the constant’s type, the placement of theconst qualifier is important. These definitions are read backward, meaning that in this case, EOCStringConstant is a “constant pointer to an NSString.” This is what we want; the constant should not be allowed to change to point to a different NSString object.

The extern keyword in the header tells the compiler what to do when it encounters the constant being used in a file that imports it. The keyword tells the compiler that there will be a symbol for EOCStringConstant in the global symbol table. 

This means that the constant can be used without the compiler’s being able to see the definition for it

The compiler simply knows that the constant will exist when the binary is linked.

The constant has to be defined once and only once. It is usually defined in the implementation file that relates to the header file in which it is declared. The compiler will allocate storage for the string in the data section of the object file that is generated from this implementation file. 

When this object file is linked with other object files to produce the final binary, the linker will be able to resolve the global symbol for EOCStringConstant wherever else it has been used.

The fact that the symbol appears in the global symbol table means that you should name such constants carefully. For example, a class that handles login for an application may have a notification that is fired after login has finished. The notification may look like this:

// EOCLoginManager.h
#import <Foundation/Foundation.h>

extern NSString *const EOCLoginManagerDidLoginNotification;

@interface EOCLoginManager : NSObject
- (void)login;

// EOCLoginManager.m
#import "EOCLoginManager.h"

NSString *const EOCLoginManagerDidLoginNotification =

@implementation EOCLoginManager

- (void)login {
    // Perform login asynchronously, then call 'p_didLogin'.

- (void)p_didLogin {
    [[NSNotificationCenter defaultCenter]


Note the name given to the constant. Prefixing with the class name that the constant relates to is prudent and will help you avoid potential clashes. This is common throughout the system frameworks as well. UIKit, for example, declares notification names as global constants in the same way. The names includeUIApplicationDidEnterBackgroundNotification andUIApplicationWillEnterForegroundNotification.

The same can be done with constants of other types. If the animation duration needed to be exposed outside of the EOCAnimatedView class in the preceding examples, you could declare it like so:

// EOCAnimatedView.h
extern const NSTimeInterval EOCAnimatedViewAnimationDuration;

// EOCAnimatedView.m
const NSTimeInterval EOCAnimatedViewAnimationDuration = 0.3;

Defining a constant in this way is much better than a preprocessor define because the compiler is used to ensure that the value cannot change. Once defined in EOCAnimatedView.m, that value is usedeverywhere. A preprocessor define could be redefined by mistake, meaning that different parts of an application end up using different values.

In conclusion, avoid using preprocessor defines for constants. Instead, use constants that are seen by the compiler, such as static const globals declared in implementation files.

Things to Remember

Image Avoid preprocessor defines. They don’t contain any type information and are simply a find and replace executed before compilation. They could be redefined without warning, yielding inconsistent values throughout an application.

Image Define translation-unit-specific constants within an implementation file as static const. These constants will not be exposed in the global symbol table, so their names do not need to be namespaced.

Image Define global constants as external in a header file, and define them in the associated implementation file. These constants will appear in the global symbol table, so 



Effective Objective-C 2.0:Item 48: Prefer Block Enumeration to for Loops

Item 48: Prefer Block Enumeration to for Loops Enumerating a collection is a very common task in ...

Effective Objective-C 2.0: Item 41: Prefer Dispatch Queues to Locks for Synchronization

Item 41: Prefer Dispatch Queues to Locks for Synchronization Sometimes in Objective-C, you will com...

Effective Objective-C 2.0: Item 30: Use ARC to Make Reference Counting Easier

Item 30: Use ARC to Make Reference Counting Easier Reference counting is a fairly easy concept to...

Effective Objective-C 2.0:Item 15: Use Prefix Names to Avoid Namespace Clashes

可惜没有指出怎么改第三方library名字的具体操作 Item 15: Use Prefix Names to Avoid Namespace Clashes Unlike other lan...

Effective Objective-C 2.0: Item 35: Use Zombies to Help Debug Memory-Management Problems

Item 35: Use Zombies to Help Debug Memory-Management Problems Debugging memory-management issues ...

Effective Objective-C 2.0: Item 39: Use Handler Blocks to Reduce Code Separation

Item 39: Use Handler Blocks to Reduce Code Separation A common paradigm in programming a user int...

Effective Objective-C 2.0: Item 43: Know When to Use GCD and When to Use Operation Queues

Item 43: Know When to Use GCD and When to Use Operation Queues GCD is a fantastic technology, but...

Effective C++(Item1) Prefer const and inline to #define

1 compare const and #define   This can be called perfer the compiler(编译器) to the preprocessor(预处理器)...

Effective Objective-C 2.0:Item 50: Use NSCache Instead of NSDictionary for Caches

Item 50: Use NSCache Instead of NSDictionary for Caches A common problem encountered when develop...

Effective Objective-C 2.0:Item 26: Avoid Properties in Categories

Item 26: Avoid Properties in Categories A property is a way of encapsulating data (see Item 6)....