该系列是《Effective Objective-C 2.0——编写高质量iOS与OS X代码的52个有效方法》的读书笔记。
第一条:了解Objective-C语言的起源
同C++类似,Objective-C也是C语言进行面相对象化的扩展。二者存在一个根本性的区别:
- C++是一种基于函数调用的语言,运行时执行的代码由编译器决定。某个对象试图调用哪一个函数将在编译过程中确定好。只有在实现了多态函数的前提下,运行时才会按照虚函数表动态查找实际调用的函数。
- Objective-C是一种基于消息结构的语言,运行时所执行的代码由运行环境决定,在编译时并不会查找目标对象要执行的方法。编译器甚至不会实际关心接收消息的对象的类型,而是留到运行时由动态绑定进行处理。同C++相比,可以认为Objective-C永远都是多态的。
也正是因为这种原因,Objective-C语言所有的重要工作都由运行时组件而非编译器来实现,该组件包含了所有Objective-C面向对象特性所需的数据结构和函数。举例说明,Objective-C编写的应用大多运行在iOS操作系统上,Objective-C的运行时组件可以认为是属于iOS的一部分。在查阅Objective-C文档时我们也经常发现,某个类某个方法的有效范围通常是给定的某几个iOS的版本(如iOS 6.0以后、iOS 3.0~6.0等),而不是XCode 4.5、XCode 5.1等编译工具的版本。这也说明Objective-C的函数更多的与iOS系统的版本相关联而非IDE。
在Objective-C中,所有的对象都分配在堆空间中,不会直接分配在栈空间。对象创建后将对象的地址返回一个指针用于对对象进行操作。另外有一些类比较特殊,已CGRect等为例,该类直接定义在栈中,已实力的形式存在而不是只想对象的指针。因为CGRect等类实际上是C的结构体,而且结构较为简单。采用这种方式有助于提升系统的性能。
第二条:尽量避免类的头文件中引入其他头文件
我们知道,在工程中新建一个Objective-C类,会添加头文件(.h)和源文件(.m)两个文件。头文件中声明类的公有成员和API接口,源文件中实现类的方法。如果需要暴露的接口参数或公有成员为某一个Objective-C类,那么可以使用前向声明而非引入另一个类的头文件的方式解决:
@class SomeClassName;
如果直接引入头文件,那么头文件所暴露给使用者的内容就会过多(尤其是连续很多级import之后),可能造成编译时间较长。使用前向声明则很好地解决了这个问题,减少了类的使用者的头文件的数量。而且解决了循环import可能造成的无法正确编译的问题。
有时候必须在头文件中包含其他头文件,主要有两种情况,其一是该类继承自其他某个类,另一种情况是当前类复合某种协议,协议的定义应单独放置于一个头文件中。而在代理(delegate)方法中,不需要添加独立的头文件。代理方法与实现代理的类写在同一个文件中。