首页 > 代码库 > 《Effective Objective-C 2.0》—(第1-5条)—熟悉Objective-C

《Effective Objective-C 2.0》—(第1-5条)—熟悉Objective-C

    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 = http://www.mamicode.com/@{@"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宏来定义枚举类型,并指明底层数据类型。这样做可以确保枚举是开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。


其余章节点击这里