Objective-C与C++、Java等面向对象语言类似,不过很多方面还是有差别的。如果你用过另外一种面向对象的语言,那就能理解Objective-C所用的许多范式和模板。但是语法上还是有陌生感。因为该语言使用了“消息结构”而非“函数调用”。(Objective-C语言由Smalltalk演化而来,后者是消息型语言的鼻祖)
// Objective-C
Object *obj = [Object new];
[obj performWith:parameter1 and:parameter2];
// C++
Object *obj = new Object;
obj->perform(parameter1, parameter2);
区别:消息结构的语言,其运行时所执行的代码由运行环境来决定,而函数调用的语言,则由编译器决定。如果示例代码中调用的函数是多态的,那么在运行时要按照“虚方法表”来查找到底要执行哪个函数实现。但是采用消息结构的语言,无论是否多态,总是在运行期才会去查找所要执行的方法。事实上,编译器甚至不关心接收消息的对象的什么类型,接收消息的对象问题也要在运行时处理,这个过程叫做“动态绑定”。
Objective-C的重要工作都由“运行期组件”来完成,而不是“编译器”。使用Objective-C的面向对象特性所需的全部数据结构及函数都是在“运行期组件”里面(全部内存管理方法也在)。“运行期组件”的本质上就是一种和开发者所编代码相连接的”动态库“,其代码能把开发者编写的所有程序粘合起来。这样的话,只要更新”运行期组件“,就能提升程序性能。但是在”编译器“完成的语言,则需要重新编译代码,才可以提升性能。
Objective-C是C语言的“超集”,C语言的所有功能在Objective-C中都适用。其中我们要理解C语言的内存模型,这有助于我们理解Objective-C的内存模型和“引用计数”机制的工作原理。
Objective-C语言中的指针是用来指示对象的,声明一个变量,令其指代某个对象:
NSString *someStr = @"The string";
声明了一个名为:someStr的变量,类型是:NSString *,也就是说,此变量为指向NSString的指针(指针就是地址)。所有的Objective-C都必须这样声明。因为对象所占内存必须分配在“堆空间”,绝不能分配在“栈”上。
someStr变量指向分配在堆里面的某块内存,其中含有一个NSString对象。如果再创建一个变量,让它指向同一地址,那么并不拷贝改对象,只是2个变量会同时指向此对象:
NSString *someStr = @"The string";
NSString *anotherStr = someStr;
只有一个NSString实例,却有2个变量指向此实例,类型都是NSString *。这说明“栈”里分配了2块内存,每块的大小能存下一枚指针,且值是一样的,就是NSString实例的内存地址。
分配在堆中的内存必须直接管理,分配在栈上的用于保存变量的内存则会在其栈帧弹出时自动清理。Objective-C将堆内存管理抽象出来了,不需要malloc和free来分配和释放对象所占的内存了。Objective-C运行期环境吧这部分工作抽象为一套“内存管理框架”--“引用计数”。
在Objective-C中,有时会遇到定义里没有“*”的变量,它们可能会使用“栈空间”。这些变量保存的不是Objective-C对象。比如:CoreGraphics框架中的CGRect就是。
struct CGRect {
CGPoint origin;
CGSize size;
}
typedef struct CGRect CGRect;
整个系统框架都在使用这样的结构体,因为如果改用Objective-C对象的话,性能将会受到影响,因为创建对象需要额外的开销,例如分配和释放内存等。如果需要保存int、float、double、char等“非对象型”,那么使用像CGRect这样的结构体就足够了。
要点:
1.Objective-C给C语言添加了面向对象的特性,是其超集。Objective-C使用动态绑定的消息结构,也就说,在运行期才会去检查对象类型。接收一条消息之后,究竟应该执行什么代码,由运行期环境决定,而不是编译器。
2.理解C语言的核心概念有助于写好Objective-C程序,尤其是掌握内存模型与指针。