本系列是根据《Effective Objective-C 2.0》一书中的系列文章,选开发中实践的经验之谈,聚集于此,便于查阅,或者为来访者提供一份参考。编排按《Effective Objective-C 2.0》中条目。html
在类的头文件中尽可能少引用其余头文件程序员
@class
objective-c
"向前声明"或“前向引用”,仅仅是声明一个类名,并不会包含类的完整声明。 @class
还能解决循环包含的问题。在实现这个接口的实现类中,若是须要引用这个类的实体变量或者方法之类的,仍是须要#import
在@class
中声明的类进来。express
那么为何还须要@class
呢?由于做声明某个类来用,编译器并不会将类的实例变量或方法引入,其能够加快编译,减小编译时间。数组
另外,在实际中,会遇到——当解析某个文件时,编译器会发现它引入了另外一个头文件,而那个头文件又回过头来引入了第一个头文件——循环包含,这时候,使用#import
而非#include
指令虽然不会致使死循环,但却意味着两个类里有一个没法被正确编译。这时候,采用@class
仅做声明。安全
#import
和#include
都能完整地包含某个文件的内容,#import
能防止同一个文件被包含屡次;#import <>
用来包含系统自带的文件,#import “”
用来包含自定义的文件多用字面量语法,少用与之等价的方法。app
字面量语法,实际上是Objective-C 2.0
添加的“语法糖”,方便程序员书写,提升可读性以及编译时检查等特性。框架
其中,涉及到类NSString
、NSNumber
、NSArray
、NSDictionary
。post
NSString *siteTitle = @"Mobiletuts+";
//假如不用字面量语法,那么上面可能会写成
NSString *siteTitle = [NSString stringWithUTF8String:"Mobiletuts"];
复制代码
NSNumber *boolYES = [NSNumber numberWithBool:YES];
NSNumber *boolNO = [NSNumber numberWithBool:NO];
NSNumber *charX = [NSNumber numberWithChar:'X'];
NSNumber *fortySevenInt = [NSNumber numberWithInt:47];
NSNumber *fortySevenUnsigned = [NSNumber numberWithUnsignedInt:47U];
NSNumber *fortySevenLong = [NSNumber numberWithLong:47L];
NSNumber *goldenRatioFloat = [NSNumber numberWithFloat:1.61803F];
NSNumber *goldenRatioDouble = [NSNumber numberWithDouble:1.61803];
复制代码
采用字面量语法,上面可写为:spa
NSNumber *boolYES = @YES;
NSNumber *boolNO = @NO;
NSNumber *charX = @'X';
NSNumber *fortySevenInt = @47;
NSNumber *fortySevenUnsigned = @47U;
NSNumber *fortySevenLong = @47L;
NSNumber *goldenRatioFloat = @1.61803F;
NSNumber *goldenRatioDouble = @1.61803;
复制代码
字面量也适用于下述表达式:
int x = 5
float y = 6.32f
NSNumber *expressionNumber = @{x * y}
复制代码
NSArray *instruments = [NSArray arrayWithObjects: @"Ocarina", @"Flute", @"Harp", nil];
复制代码
使用字面量语法来建立:
NSArray *instruments = @[ @"Ocarina", @"Flute", @"Harp" ];
复制代码
假如,要声明一个NSMutableArray
数组,能够采用下面: NSMutableArray *instrumentsMutable = [ @[ @"Ocarina", @"Flute", @"Harp" ] mutableCopy];
最后,须要注意的是,在用字面量语法建立数组时,若数组中有元素有nil,则会抛出异常,由于字面量语法实际上只是一种语法糖,其效果等同因而先建立了一个数组,而后把方括号内的多有对象都加到这个数组中。抛出的异常以下:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',reason:'*** - [__NSPlaceholderArray initWithObjects:count:]:attempt to insert nil object form objects[0]'
复制代码
在改用字面量语法来建立数组时就会遇到这个问题,下面这段代码分别以两种语法建立数组:
id object1 = /*...*/
id object2 = /*...*/
id object3 = /*...*/
NSArray *arrayA = [NSArray arrayWithObjects:object1,object2,object3,nil];
NSArray *arrayB = @[object1,object2,object3];
复制代码
假如,object1
和object3
都指向了有效对象,而object2
是nil
,会出现什么状况?
按字面量建立的arrayB会抛出异常,arrayA
虽然能建立出来,可是其中只有object1
一个对象。缘由在于,“arrayWithObjects”方法会一次处理各个参数,直到发现nil
,object2
是nil
,因此方法提早结束。
这个微秒的差异代表,使用字面量语法更为安全。抛出异常令应用程序终止执行,这比建立好数组以后才发现元素个数少了要好。
建立一个字典:
NSArray *keys = [NSArray arrayWithObjects:@"Character", @"Weapon", @"Hitpoints", nil];
NSArray *objects = [NSArray arrayWithObjects:@"Zelda", @"Sword", [NSNumber numberWithInt:50], nil];
NSDictionary *stats = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
复制代码
或者,采用更简洁的写法:
NSDictionary *stats = [NSDictionary dictionaryWithObjectsAndKeys:
@"Zelda", @"Character",
@"Sword", @"Weapon",
[NSNumber numberWithInt:50], @"Hitpoints",
nil];
复制代码
而采用字面量语法:
NSDictionary *stats = @{ @"Character" : @"Zelda",
@"Weapon" : @"Sword",
@"Hitpoints" : @50 };
复制代码
一样,在用字面量语法中,若是值是nil
,与字面量建立数组表现类似,会抛出异常。
“取下标”操做通常会用objectAtIndex
方法:
NSString * flute = [instruments objectAtIndex:1];
NSString * flute = instruments[1];
复制代码
假如,是可变数组,采用字面量写法:
instrumentsMutable[0] = @"Ocarina of Time";
//其对应的方法为:replaceObjectAtIndex:withObject
复制代码
字典的读写也相似。
NSString *name = stats[@"Character"]; // Returns 'Zelda'
statsMutable[@"Weapon"] = @"Hammer";
//分别对应方法:
//objectForKey:
//setObject:forkey:
复制代码
多用类型常量,少用#define预处理指令。
编写代码中常将常量写为:
#define ANIMATION_DURATION 0.3
复制代码
预处理过程会把全部ANIMATION_DURATION
一概替换成0.3
,假设该指令声明在某个头文件中,那么全部引入了这个头文件的代码,其ANIMATION_DURATION
都会被替换。
更好的方式是:
static const NSTimerInterval kAnimationDuration = 0.3
复制代码
首先,添加了类型信息,清楚地描述了常量的含义。
其次,要注意常量的名称,经常使用的命名法是:若常量局限于某“编译单元(通常只实现文件,即.m)”以内,则在前面加k
;若常量在类以外可见,则一般以“类名”为前缀。
最后,要注意常量的位置。咱们总喜欢在头文件里声明预处理指令,这是至关糟糕的,当常量名称有可能互相冲突更是如此。因为OC没有“命名空间”这一律念,因此避免将常量声明放在头文件里。即便采用static const
这种方式也是如此。若不打算公开某个常量,则应该将其定义在使用该常量的实现文件里。
那么,为何要用static
和const
来修饰常量?
static
代表的是做用域,意味着该变量尽在定义此变量的编译单元中可见,编译器每收到一个编译单元,就会输出一份“目标文件”。在Objective-C语境下,“编译单元”一次一般指每一个类的实现文件。假如声明此变量时,不加static,那么编译器会为它建立一个“外部符号(external symbol)”。此时,若其余编译单元也声明了同名变量,就会抛出一条错误消息:
duplicate symbol _kAnimationDuration in:
EOCAnimatedView.o
EOCOtherView.o
复制代码
const
则声明为不可修改。
实际上,若是一个变量既声明为static
,又声明为const
,那么编译器根本不会建立符号,而是会像#define
预处理指令同样,把全部遇到的变量头替换为常值,可是,这种方式具备类型信息。
那么,假如要对外公开一个常量要怎么办?
有时候,须要对外公开常量。常见的情景就是在类代码中调用NSNotificationCenter
以通知他人。那么通知名通常声明一个外界可见的常值变量。
此类变量须要放在“全局符号表(global symbol table)”中,以即可以在定义该常量的编译单元以外使用。其定义方式:
//in the header file
extern NSString *const EOCStringConstant;
//in the implementation file
NSSting *const EOCStringConstant = @"VALUE"
复制代码
编译器发现头文件中含有extern
,就知道,在全局符号表中将会有一个EOCStringConstant
的符号。即编译器无须查看其定义,就容许代码中使用此常量。由于它知道,当连接二进制文件后,确定能找到这个常量。
此类常量必需要定义,并且只能定义一次。一般将其定义在与声明该常量的头文件相关的实现文件里。由实现文件生成目标文件时,编译器会在“数据段”为字符串分配存储空间。
static const
来定义“只在编译单元内可见的常量”。因为此类常量不在全局符号表中,因此无须为其名称加前缀;extern
来声明全局变量,并在相关实现文件中定义其值。这种常量要出如今全局符号表中,因此其名称应加以区隔,一般用与之相关的类名作前缀。用枚举表示状态、选项、状态码
enum {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
typedef enum {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
} UITableViewCellStyle;
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
NS_OPTIONS
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
复制代码
在iOS开发中,凡是须要以按位或操做来组合的枚举都应使用NS_OPTIONS
定义。
如果枚举不须要互相组合,则应使用NS_ENUM
来定义。
NS_ENUM
与NS_OPTIONS
宏定义了来定义枚举类型,并指明底层数据类型。以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型;switch
语句中不要实现default
分支。这样的话,假如新枚举以后,编译器就会提示开发者:switch
语句并未处理全部枚举。《Effective Objective-C 2.0》