Effective Objective-C 2.0 读书笔记 -- 熟悉Objective-C语言
看到Effective这个词,大家一定会想到《Effective C++》、《Effective Java》等业界名著,那些书里汇聚了多项实用技巧,又系统而深入的讲解了各种编程知识。那么,《Effective Objective-C 2.0》也是如此。
作为Mac OS X与iOS应用程序的开发语言,Objective-C作为首选。那么,它有哪些需要注意的呢?
起源
Objective-C与C++、Java一样,是面向对象的语言,是由Smalltalk演化而来。Smalltalk是消息型语言的鼻祖。消息与函数调用之间的区别看上去就像这样:
1 | //Messaging (Objective-C) |
关键区别在于:使用消息结构的语言,其运行时所应执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器决定。
Objective-C是C的“超集”(superset),所以C语言中的所有功能在编写Objective-C代码时依然适用。理解C语言的内存模型(memory model),有助于理解Objective-C的内存模型及其“引用计数”(reference counting)机制的工作原理。Objective-C语言中的指针是用来指示对象的。
关于使用头文件
主要使用 import
关键字。然而,我们在 .h
文件中一般首选使用 @class
关键字,它能“向前声明”一个类。对于不需要知道类细节的情况下我们使用它。否则不会轻易使用 import
来引入整个头文件。
过多的引入头文件,会增加编译时间。这就是我们多使用 @class
关键字的直接原因。
除非确有必要,否则不要引入头文件。一般来说,应在某个类的头文件中使用“向前声明”来提及别的类,并在实现文件中引入那些类的头文件。这样做可以尽量降低类之间的耦合(coupling)。
有时无法使用“向前声明”,比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵循某协议”的这条声明移至“class-continuation分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入。
字面量语法
在编写Objective-C程序时,总会用到某几个类,它们属于Foundation框架。虽然从技术上来说,不用Foundation框架也能写出Objective-C代码,但是实际上却经常要用到此框架。这几个类是NSString、NUNumber、NSArray、NSDictionary。从类名上即可看出各自所表示的数据结构。
Objective-C以语法繁杂而著称。不过从Objective-C 1.0起,有一种简单的方式能创建NSString 对象。这就是“字符串字面量”(string literal),其语法如下:
1 | NSString *string = @"Effective Objective-C 2.0"; |
字面数值
1 | NSNumber *number = [NSNumber numberWithInt:10]; |
更多表示:
1 | NSNumber *intNumber = @11; |
字面量语法也适用于下述表达式
1 | int x =5; |
字面量数组
1 | NSarray *animals = [NSArray arrayWithObjects:@"cat", @"dog", @"mouse", @"badger", nil]; |
使用数组
1 | NSString *dog = [animals objectAtIndex:1]; |
字面量字典
1 | NSDictionary *personData = [NSDictionary dictionaryWithObjectsAnsKeys:@"Matt", @"firstName", @"Galloway", @"lastName", [NSNumber numberWithInt:28], @"age", nil]; |
使用字典
1 | NSString *lastName = [personData objectForKey:@"lastName"]; |
可变数组和字典
1 | [mutableArray replaceObjectAtIndex:1 withObject:@"dog"]; |
局限性
字面量语法有个小小的限制,就是除了字符串以外,所创建出来的对象必须属于Foundation框架才行。如果自定义了这些类的子类,则无法用字面量语法创建其对象。要想创建自定义子类的实例,必须采用“非字面量语法”(nonliteral syntax)。
使用字面量语法创建出来的字符串、数组、字典对象都是不可变的(immutable)。若想要可变版本的对象,则需要复制一份:
1 | NSMutableArray *mutable = [@[@1, @2, @3, @4] mutableCopy]; |
这么做会多调用一个方法,而且还要再创建一个对象,不过使用字面量语法所带来的好处还是多于上述缺点的。
用字面量语法创建数组或字典时,若值中有nil,则会抛出异常。因此,务必确保值里不含nil。
多用类型常量 少用#define预处理指令
编写代码时经常要定义常量。掌握了Objective-C与其C语言的基础的人,也许会用这种方法来做:
1 | #define ANIMATION_DURATION 0.3 |
上述预处理指令会把源代码中的ANIMATION_DURATION字符串替换为0.3.预处理过程会把碰到的所有ANIMATION_DURATION一律替换成0.3,这样的话,假设此指令声明在某个头文件中,那么所有引入了这个头文件的代码,其ANIMATION_DURATION都会被替换。
要解决此问题,应该设法利用编译器的某些特性才对。
1 | static const NSTimeInterval kAnimationDuration = 0.3; |
用此方式定义的常量包含类型信息,其好处的清楚地描述了常量的含义。
常用的命名法是:
- 若常量局限于某”编译单元”(translation unit,也就是“实现文件”,implementation file)之内,则在前面加字母k;
- 若常量在类之外可见,则通常以类名为前缀。
定义常量的位置很重要。在头文件里声明预处理指令,这样会增加常量名称互相冲突的可能性。
在头文件中使用extern来声明全局常量,并在相关实现文件中定义其值。这种常量要出现在全局符号表中,所以其名称应加以区隔,通常用与之相关的类名做前缀。
枚举使用
枚举只是一种常量命名方式。某个对象所经历的各种状态就可以定义为一个简单的枚举集(enumeration set)。
1 | enum IHConnectionState { |
默认情况下,枚举起始值为0,以后依次递增,1,2,3…
其实还可以我们自己指定枚举值:
1 | enum IHConnectionState { |
也可以定义为位移值:
1 | enum UIViewAutoresizing { |
关于枚举,Foundation框架中定义了一些辅助的宏,用这些来定义枚举类型时,也可以指定用于保存枚举值的底层数据类型。
1 | typedef NS_ENUM(NSUInteger, IHConnectionState) { |
这些宏的定义如下:
1 |
|
第一个#if用于判断编译器是否支持新式枚举。如果不支持,那么就用老式语法来定义枚举。
在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举。