Objective-C通过一套全新的语法,在C语言基础上添加了面向对象特性。OC的语法中频繁使用中括号([ ]),而且不吝于写出极长的方法名,这通常令许多人觉得此语言较为冗长。这是这样写出来的代码非常易读,只是C++和Java程序员不太适应。
OC语言学起来很快,但有很多微妙细节需要注意,而且还有许多容易为人所忽略的特性。另一方面,有些开发者并未完全理解或是容易滥用某些特性,导致写出来的代码难以维护,难以调试。本章讲解基础知识,后续各章语言及其相关架构的各个特定话题。
第1条:了解OC语言的起源
OC与C++,Java等面向对象语言类似,不过很多方面有差别。若是用过另一种面向对象语言,那么就能理解OC所用的许多范式与模板了。然而语法上也许会显得陌生,因为OC使用消息结构(messaging structure)而非函数调用(function calling)。OC由SmallTalk演化而来,后者是消息型语言的鼻祖。消息与函数调用之间的区别看上去就像这样:
//Messaging (OC) Object *obj = [Object new]; [obj perfromWith:parameter1 and:parameter2]; //Function calling(C++) Object *obj = new object; obj->perform(parameter1,parameter2);
关键区别在于:使用消息结构的语言,其运行时所应执行的代码由运行环境决定;而使用函数调用的语言,由编译器决定。如果范例代码中调用函数是多态的,那么在运行时就要按照“虚函数表”(virtual table)来查找到底应该执行哪个函数。而采用消息结构的语言,不论是否多态,总是在运行时才会去查找所要执行的方法。实际上,编译器甚至不关心接受消息的对象是何种类型。接受消息的对象问题也要在运行时处理,其过程叫做“动态绑定”(dynamic
binding)见第11条
OC的重要工作都由“运行期组件”(runtime component)而非编译器来完成。使用OC的面向对象特性所需的全部数据结构及函数都在运行期组件里面。举例来说,运行期组件中含有全部内存管理方法。运行期组件本质上就是一种与开发者所编代码相链接的“动态库”(dynamic library),其代码能把开发者编写的所有程序战粘和起来。这样,只需更新运行期组件,即可提升应用程序性能。
OC是C的超集,所以C语言中的所有功能在编写OC代码时依然适用。
OC只能在堆上声明变量,不能在栈上声明(CGRect可以声明在栈上,因为CGRect是C结构体),OC将堆内存管理抽象出来了。不需要malloc及free来分配或释放对象所占内存。OC运行期环境把这部分工作抽象为一套内存管理架构,名叫“引用计数”,见第29条
第2条:在类的头文件中尽量少引入其他头文件
与C和C++一样,OC使用头文件与“实现文件”来区隔代码。用OC语言编写任何类几乎都要引入Foundation.h。
尽量在头文件中使用类声明,防止循环头文件引用,过多的引用头文件也会增加编译时间。
第3条:多用字面量语法,少用与之等价的方法
举例:
NSNumber *someNumber = @1; NSNumber *intNumber = @1; NSNumber *floatNumber = @2.5f; NSArray * animals = @[@"cat",@"dog",@"mouse",@"badger"]; NSString *dog = animals[1]; //---------------------- id object1 = /*......*/; id object2 = /*......*/; id object3 = /*......*/; NSArray* arrayA = [NSArray ArrayWithObjects:object1,object2,object3,nil]; NSArray* arrayB = @[object1,object2,object3]; //如果object2 = nil那么arrayB抛出异常,array中只有object1和object2。 //---------------------- NSDictionary *personData = @{@"firstName":@"Matt",@"lastName":@"Galloway",@"age":@28};
第4条:多用类型常量,少用#define预处理指令
编写代码经常需要定义常量。例如:
#define ANIMATION_DURATION 0.3
#define会编译期简单替换文本,会造成很多不必要的麻烦。要像解决此问题,应该设法利用编译器的某些特性才行。比如:
static const NSTimeInterval kAnimationDuration = 0.3;
这种方式定义常量包含了类型信息,且最好将这个声明放到.m文件中。static const的声明不应该出现在头文件里。因为OC没有命名空间,所以那样就等于声明了一个全局变量。static修饰表示该变量仅定义在此.m文件中(一个编译单元),如果不加static,则编译器会为他创建一个“外部符号”(external symbol)。此时若另外一个.m文件也有了同名变量,就会造成符号重复(duplicate symbol)。
实际上,如果一个变量既声明为static,又声明为const,那么编译器根本不会创建符号,而是会像#define一样预处理指令一样,把所有遇到的变量都替换为常量。
有时候需要对外公开某个常量,例如:
//in the header file extern NSString *const EOCStringConstant; //in the implement file NSString* const EOCStringConstant = @"VALUE";
EOCStringConstant是一个常量指针,编译器会看到头文件中的extern关键字,这个关键字是要告诉编译器,在全局符号表中将会有有一个名叫EOCStringConstant的符号。当链接成二进制文件之后,肯定能找到这个常量。因为符号要放在全局符号表里,所以命名常量时需谨慎。
本节要点:
● 不要用预处理指令定义常量。即使有人重新定义了常量值,编译器页不会产生警告信息,这将导致程序中常量不一致。
● 在实现文件中使用static const来定义“只在编译单元内可见的常量”(translation-unitspecific constant)。此类常量不在全局符号表中。
● 在头文件中使用extern来声明全局常量,并在实现文件中定义其值。
第5条:用枚举表示状态、选项、状态码
直接举例
typedef NS_ENUM(NSUInteger,EOCConnectionState){ EOCConectionStateDisconnected, EOCConectionStateConnecting, EOCConectionStateConnected, };
● 用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明底层数据类型。这样做可以确保枚举是开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。
《Effective Objective-C 2.0》—(第1-5条)—熟悉Objective-C