现在的位置: 首页 > 综合 > 正文

Effective Object-C 2.0 第一章(条目4)

2018年03月22日 ⁄ 综合 ⁄ 共 4055字 ⁄ 字号 评论关闭

translated by my gf.

条目4:使用带类型的常量而不是预编译宏#define

在编写代码时,你经常需要定义一个常量。例如:考虑一个使用动画自动呈现和消失的UI视图类。动画的持续时间是你将要指定的典型常量。你已经学习了Obective-C的全部和它的C语言基础,因此你将会这样定义常量:

#define ANIMATION_DURATION 0.3

    这是一个预编译指令;只要在你的源代码中发现ANIMATION_DURATION,就替换成0.3。这看起来似乎符合要求,但这样的定义没有类型信息。这表明可能有变量声明成“duration”并和时间有关,但是并不明确。预编译器也将会盲目的替换所有出现ANIMATION_DURATION的地方。如果它是在头文件里声明,任何导入这个头文件的都将被替换。

    为了解决这个问题,你应该充分利用编译器的功能。有一种比使用预编译宏定义常量的更好的方式。例如:以下定义了一个NSTimeInterval类型的常量。

static const NSTimeInterval kAnimationDuration= 0.3;

注意这种风格有类型信息,它是很有帮助的,因为它明确地定义了这个常量是什么。它的类型是NSTimeInterval,,因此它帮助我们记录变量的使用。如果需要定义很多常量,这无疑能帮助你和后来阅读代码的其他人。

同时需要注意的是常量是如何命名的。对于本地编译单元的常量,一般在前面加字母K;对于那些暴露到外部类的常量,通常在前面加类名。条目19解释了更多的命名惯例。

你在什么地方定义常量非常重要。有时候,在头文件中声明预编译宏非常有吸引力,但那是一个糟糕的做法,尤其是没有使用上面命名方式的情况。例如:ANIMATION_DURATION常量出现在头文件中是一个不好的名字。它将存在于其他导入这个头文件的所有文件中,甚至它是static const类型也不应该出现在头文件中。由于Objective-C没有命名空间,它将会声明一个称为kAnimationDuration的全局变量。它的名字应该以它使用的类名开头,例如:EOCViewClassAnimationDuration。更清楚的使用命名模式详见条目19。

不需要暴露出来的常量应该定义在它的实现文件中。例如:UIView使用在UIKit的iOS应用程序中。如果需要在UIView子类中使用动画持续时间常量,应该这样使用:

// 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

变量声明为static和 const非常重要。const标识符意味着当你试图修改值时编译器会抛出异常。在这种情况下,确实是它需要的。该值不允许被修改。Static标识符意味着变量在它所定义的作用范围只在当前文件。编译器接收用来产生目标文件的输入叫做编译单元。以Objective-C为例,这通常意味着每个类,每个.m文件都有一个编译单元。因此在前面的例子中,kAnimationDuration的作用域只在由EOCAnimatedView.m生成的目标文件。如果变量不声明成static,编译器将为它创建一个外部标识符。如果另外一个编译单元声明了同样的变量名字,链接器将会抛出类似于下面的异常:

duplicate symbol _kAnimationDurationin:

EOCAnimatedView.o

EOCOtherView.o                                                                                                             

   事实上,当把变量定义成static和const,编译器根本没有创建标识符,取而代之的是就像预处理器一样进行替换。记住,这样做的好处是展现出类型信息。

    有时,你想要暴露出一个外部常量。例如,你想要使你的类要通知其他的类,你需要使用NSNotificationCenter。它的工作原理是:通过一个对象推送通知,其它的对象通过注册通知来接收消息。通知有一个字符串类型的名字,这个名字是你想要声明成外部可见的常量。这样做意味着任何人想要注册接收通知,可以不需要知道通知的确切名字,但可以简单地使用常量。

 要在定义他们的编译单元的外部使用,这种常量需要出现在全局符号表里。因此,这些常量需要以不同于static const例子的方式声明。这些变量应该这样定义:

 // In the header file

extern NSString *const EOCStringConstant;

// In the implementation file

NSString *const EOCStringConstant = @"VALUE";

  常量在头文件中声明,在实现文件中定义。在常量类型中,const标识符的位置很重要。编译器从前向后解析常量的定义,也就是说,在这种情况下,EOCStringConstant是一个“指向NSString的常量指针”,这是我们想要的;常量不应该被允许改变以指向不同的NSString对象。头部的extern关键字告诉编译器在导入它的文件使用此常量该如何做。extern关键字告诉编译器在全局字符表里面有一个叫EOCStringConstant的标识符,这意味着常量可以被使用,即使编译器还没有看到它的定义。编译器只知道,当二进制链接的时候存在该常量。

常量只能被定义一次,仅仅一次。它通常被定义在实现文件中,并与它它声明的头文件关联。

编译器在.m产生的目标文件的数据区中给常量分配存储空间。在此目标文件和其他目标文件进行链接产生最终的二进制文件的过程中,链接器可以解析在任何地方使用的EOCStringConstant全局标识符。

    标识符出现在全局标识符表中,这意味着你应该谨慎慎用这种常量。例如,一个处理应用程序登陆的类可能有登陆后完成时激发的通知。

通知像这样:

 // 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

 注意常量的命名规范。以和常量关联的类名为前缀是明智的做法,它可以帮助你避免潜在的冲突。这在系统框架中是很常见的。例如,UIKit,以同样的全局常量方式声明通知名字。这些名字包括:UIApplicationDidEnterBackgroundNotification和

UIApplicationWillEnterForegroundNotification.

    其它类型的常量也可以这么做。如果前面例子的动画持续时间需要被EOCAnimatedView类的外部使用,你可以这样定义:

// EOCAnimatedView.h

extern const NSTimeInterval EOCAnimatedViewAnimationDuration;

// EOCAnimatedView.m

const NSTimeInterval EOCAnimatedViewAnimationDuration = 0.3;

   以这种方式定义一个常量比预编译宏更好,因为使用编译器保证值不会被改变。一旦在EOCAnimatedView.m定义,值可以在任何地方使用。预编译宏可能被错误的重新定义,这意味着应用程序的不同部分可能使用不同的值。

  总之,避免使用预编译宏定义常量,取而代之的是使用编译器可见的常量标志符,如在实现文件中声明static const。

要记住的

l  避免使用预编译宏定义常量。他们不包含任何类型信息,并且仅仅是在编译之前执行查找替代。他们可以在没有警告的情况下被重新定义,在整个应用程序的不同部分导致值不一样。

l  定义内部常量:在实现文件中,将常量定义成static const。这些常量将不会出现在全局字符表里,因此他们的名字不需要有作用域的标识。

l  定义全局常量:在头文件中把全局常量声明成外部常量,并且将它们在相应的实现文件中定义。这些常量将出现在全局标志表中,因此他们的名字需要某种作用域的标识,通常在前面加上和他们一致的类名。

抱歉!评论已关闭.