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:

#define ANIMATION_DURATION 0.3

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;
@end

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

static const NSTimeInterval kAnimationDuration = 0.3;

@implementation EOCAnimatedView
- (void)animate {
    [UIView animateWithDuration:kAnimationDuration
                     animations:^(){
                         // Perform animations
                     }];
}
@end

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:
    EOCAnimatedView.o
    EOCOtherView.o

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;
@end

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

NSString *const EOCLoginManagerDidLoginNotification =
    @"EOCLoginManagerDidLoginNotification";

@implementation EOCLoginManager

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

- (void)p_didLogin {
    [[NSNotificationCenter defaultCenter]
      postNotificationName:EOCLoginManagerDidLoginNotification
                    object:nil];
}

@end

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 

编写高质量的iOS代码--Effective Objective-C 2.0 读书笔记

编写高质量的iOS代码--Effective Objective-C 2.0 读书笔记 这本书年初刷完,感觉不错,介绍了很多小点,都是平日不怎么关注的. 第1章 熟悉Objective-C...
  • uxyheaven
  • uxyheaven
  • 2014年12月26日 23:56
  • 5027

Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法(一)

1. 使用消息结构的语言,其运行时所应执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器来决定。 如果范例代码调用的函数是多态的,则运行时根据虚函数表来查出应该执行哪个函数实现。 而采用...
  • mad2man
  • mad2man
  • 2014年03月25日 16:23
  • 3035

阅读《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》总结

第1条:了解Objective-C语言的起源 Objective-C为C语言添加了面向对象特性,是其超集。Objective-C使用动态绑定的消息结构,也就是说,在运行时才会检查对象类型。接收一...
  • caojengineer
  • caojengineer
  • 2015年06月07日 22:52
  • 1155

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...
  • chuanyituoku
  • chuanyituoku
  • 2013年12月12日 21:14
  • 4815

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 ...
  • chuanyituoku
  • chuanyituoku
  • 2013年12月14日 23:29
  • 1247

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 ...
  • chuanyituoku
  • chuanyituoku
  • 2013年12月10日 21:35
  • 1279

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...
  • chuanyituoku
  • chuanyituoku
  • 2013年12月12日 15:14
  • 1090

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...
  • chuanyituoku
  • chuanyituoku
  • 2013年12月09日 09:10
  • 1128

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...
  • chuanyituoku
  • chuanyituoku
  • 2013年12月06日 11:59
  • 643

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...
  • chuanyituoku
  • chuanyituoku
  • 2013年12月12日 21:54
  • 1115
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Effective Objective-C 2.0: Item 4: Prefer Typed Constants to Preprocessor #define
举报原因:
原因补充:

(最多只允许输入30个字)