Objective-C语言(一)熟悉Objective-C

本系列是根据《Effective Objective-C 2.0》一书中的系列文章,选开发中实践的经验之谈,聚集于此,便于查阅,或者为来访者提供一份参考。编排按《Effective Objective-C 2.0》中条目。html

1、最佳实践

  • 在类的头文件中尽可能少引用其余头文件;
  • 多用字面量语法,少用与之等价的方法;
  • 多用类型常量,少用#define预处理指令;
  • 用枚举表示状态、选项、状态码;

2、实践详解

2.1 头文件

在类的头文件中尽可能少引用其余头文件程序员

@classobjective-c

"向前声明"或“前向引用”,仅仅是声明一个类名,并不会包含类的完整声明。 @class还能解决循环包含的问题。在实现这个接口的实现类中,若是须要引用这个类的实体变量或者方法之类的,仍是须要#import@class中声明的类进来。express

那么为何还须要@class呢?由于做声明某个类来用,编译器并不会将类的实例变量或方法引入,其能够加快编译,减小编译时间。数组

另外,在实际中,会遇到——当解析某个文件时,编译器会发现它引入了另外一个头文件,而那个头文件又回过头来引入了第一个头文件——循环包含,这时候,使用#import而非#include指令虽然不会致使死循环,但却意味着两个类里有一个没法被正确编译。这时候,采用@class仅做声明。安全

  • #import#include都能完整地包含某个文件的内容,#import能防止同一个文件被包含屡次;
  • #import <> 用来包含系统自带的文件,#import “”用来包含自定义的文件
  • 除非确有必要,不然不要引入头文件。通常来讲,应在某个类的头文件中使用向前声明来说起别的类,并在实现文件中引入那些类的头文件。以此来尽可能下降类之间的耦合。
  • 要声明某个类遵循一项协议,精良移至“class continuation”分类中实现。若是不行,就把该协议单独放入一个头文件中,而后将其引入。

2.2 字面量

多用字面量语法,少用与之等价的方法。app

字面量语法,实际上是Objective-C 2.0添加的“语法糖”,方便程序员书写,提升可读性以及编译时检查等特性。框架

其中,涉及到类NSStringNSNumberNSArrayNSDictionarypost

字面量字符串

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];
复制代码

假如,object1object3都指向了有效对象,而object2nil,会出现什么状况?

按字面量建立的arrayB会抛出异常,arrayA虽然能建立出来,可是其中只有object1一个对象。缘由在于,“arrayWithObjects”方法会一次处理各个参数,直到发现nilobject2nil,因此方法提早结束。

这个微秒的差异代表,使用字面量语法更为安全。抛出异常令应用程序终止执行,这比建立好数组以后才发现元素个数少了要好。

字面量字典

建立一个字典:

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:
复制代码

2.3 预处理

多用类型常量,少用#define预处理指令。

编写代码中常将常量写为:

#define ANIMATION_DURATION 0.3
复制代码

预处理过程会把全部ANIMATION_DURATION一概替换成0.3,假设该指令声明在某个头文件中,那么全部引入了这个头文件的代码,其ANIMATION_DURATION都会被替换。

更好的方式是:

static const NSTimerInterval kAnimationDuration = 0.3
复制代码

首先,添加了类型信息,清楚地描述了常量的含义。

其次,要注意常量的名称,经常使用的命名法是:若常量局限于某“编译单元(通常只实现文件,即.m)”以内,则在前面加k;若常量在类以外可见,则一般以“类名”为前缀。

最后,要注意常量的位置。咱们总喜欢在头文件里声明预处理指令,这是至关糟糕的,当常量名称有可能互相冲突更是如此。因为OC没有“命名空间”这一律念,因此避免将常量声明放在头文件里。即便采用static const这种方式也是如此。若不打算公开某个常量,则应该将其定义在使用该常量的实现文件里。

那么,为何要用staticconst来修饰常量?

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来声明全局变量,并在相关实现文件中定义其值。这种常量要出如今全局符号表中,因此其名称应加以区隔,一般用与之相关的类名作前缀。

2.4 枚举

用枚举表示状态、选项、状态码

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来定义。

  • 应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字;
  • 若是把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么久将各选项值定义为2的幂,以便经过按位或操做将其组合起来;
  • NS_ENUMNS_OPTIONS宏定义了来定义枚举类型,并指明底层数据类型。以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型;
  • 在处理枚举类型的switch语句中不要实现default分支。这样的话,假如新枚举以后,编译器就会提示开发者:switch语句并未处理全部枚举。

参考

连接

  1. Objective-C Literals Clang 3.9 documentation

  2. Objective-C Literals

书籍

《Effective Objective-C 2.0》

相关文章
相关标签/搜索