Objective-C 的对象模型

最近稍微学习了一点 Objective-C ,做笔记和做编码练习都是巩固学习的好方法。整理记录脑子里的新知识有助于理清思路,发现知识盲点以及错误的理解。

Objective-C 和 C++ 同样从兼容 C 语言开始,以给 C 语言增加面向对象为初衷,他们的出现的时间都很类似(1983 年左右)。但面向对象编程的源头却不同:C++ 受 Simula 和 Ada 的影响比较多,而 Objective-C 的相关思想源至 Smalltalk ,最终的结果是他们在对象模型上有不小的差异。

以我这些天粗浅的了解,Objective-C 似乎比 C++ 更强调类型的动态性,而牺牲了一些执行性能。不过这些牺牲,由于模型清晰,可以在今天,由更先进的编译技术来弥补了。

我对 C++ 的认知比 Objective-C 要多的多,所以对 C++ 开发中会遇到的问题的了解也多的多。在学习 Objective-C 的过程中,我发现很多地方都可以填上曾经在 C++ 开发中遇到的问题。当然,Objective-C 一定也有它自己的坑,只是我才刚开始,没有踩到过罢了。


ObjC 的类方法调用的形式,更接近于向对象发送消息。语法写作:

[obj message]

如果方法带有参数,那么就写作

[obj param:value]

方法和名称和参数的名称是一体的,参数决定了方法是什么。如果有多个参数,那么写作:

[obj param1:value1 param2:value2]

注意,如果一个类有两个方法,一个有一个参数,一个有两个参数。即使两个参数的版本中有一个参数名称和单个参数版本的相同,它们也是两个不同的方法。ObjC 不支持默认参数的语法。

C++ 调用对象的方法就更接近于 C 的函数调用。两相比较,可以发现 ObjC 的语法让代码可读性更强。你可以很容易的理解参数的用途,也不怕方法参数过多时,一串参数写漏或写错次序了。


和 C++ 一样,ObjC 的类声明和实现是分离的。但做的比 C++ 更彻底。ObjC 不能在声明的代码段中写 inline 函数。这看起来牺牲了一些运行性能,但当实现部分更好的分离。作为补充,ObjC 有 @property ,可以帮助程序员简化实现,也可以让编译器生成更好的代码。

声明一个类写成这样:

@interface class : baseclass {
   type a;
}

- (void) method;

- (void) messge: (type) param;

+ (id) create ;

@end

ObjC 利用了 C 语言中没有使用的符号 @ 来扩展 C 的语法,而不是用 C++ 里增加关键字的方式。这或许是一个对语言扩展更简单的做法,而不用考虑兼容性。C++ 就得精心挑选新增加的关键字,尽量回避那些已有代码中高频出现的单词。

类的数据段和方法是分离的。数据描述放在 {} 中,方法写在其后,在 @end 之前。

"-" 开头的方法是实例方法,也就是 C++ 中的成员方法。成员方法中可以通过 self 取到实例指针,也就是 C++ 中的 this 指针。

同样,ObjC 也支持类方法,也就是 C++ 中的 static 成员方法。通常是用来构造实例。声明方法是在方法名前写一个 + 号。

和 C++ 不同,ObjC 是有类对象的。类对象里有超类指针、类名、类方法列表指针,还有类对象的字节大小等元信息。而 C++ 中是用 RTTI 类实现不完全的类似功能的。

调用类方法和调用实例方法在语法上没有什么不同。类名就是类对象的名字。

ObjC 不支持多继承,没有私有、公开这些修饰符。

ObjC 的类方法实现必须写在同一个源文件里。不像 C++ 有 :: 操作符,ObjC 在实现方法时不写类的名字,而是把所有实现都写在 @implementation class ... @end 之间。访问基类,也可以方便的使用 super 关键字。

那么,如果一个类的方法太多,不适合写在同一个源文件中怎么办?


ObjC 提供了 category 这个概念。

可以通过 category 为一个类添加一些方法。category 和继承是不同的,不能为类添加新的成员变量,所以它不会改变类对象的内存布局。添加了方法的类还是原来那个类。

category 的语法是这样的:

@interface class (category) 

- newmethod;

@end

这样,就给 class 类添加了一个方法 newmethod ,并归类在 category 下。

和 C++ 不同,ObjC 的方法更具动态性。你可以在运行时任意调用一个对象的方法,而不用管它是否存在。ObjC 支持 id 这个类型。 id 其实就是对象指针,任何类型的对象都可以被 id 引用,并可以方便的向其发送消息(方法调用)。如果方法不存在,会抛出运行时错误。

向一个指定类型发送一个不存在的消息,会得到一个编译期警告,而不是编译错误。当然,我们不能随便忽略编译期警告,如果我们清楚的知道运行期这个对象可以处理这个消息,那么可以给类加一个 category 但不必实现它。这样,编译器就能了解新的方法了。

利用 category 可以方便的一个庞大的类拆分成独立的模块。在 C++ 中,比较接近的概念是 friend ,不过 friend 不易被优雅的使用。

既然方法可以被运行期检查,那么方法本身在 ObjC 中也可以被当成一种类型来处理。比较接近的 C++ 中的概念是 成员方法指针。回顾学习 C++ 的经历就能回忆起当年使用 ::* 或是 ->* 的头痛经历。ObjC 中的方法可以运行期绑定, @selector(method:) 的语法也简单的多。

在 NSObject 中就提供了一个叫 respondsToSelector: 的方法,接受一个 selector 用来检查自己是否可以接受这个消息。


ObjC 也提供了类似 Java 的 interface 或是 C++ 的纯虚类的东西,在 ObjC 中被称为 @protocol 。

@protocol 可以看成是一种没有数据成员的虚类。一个实际的类可以声明自己实现了某些协议,语法是

@interface class : base <protocol>
{
   // variables
}

// methods
@end

和继承不同,一个类可以声明多个协议。然后在 @implementation 中必须一一实现它们。

如上所述,ObjC 已经做到了运行期的方法绑定,所以 @protocol 只是做了更严格的编译检查。在新版的 ObjC 2.0 中,追加了 @optional 和 @required 用来描述那些方法的实现是可选的,哪些必须实现。


ObjC 的基础库比 C++ 更完整,标准化要好的多,也和语言结合的更紧密。

比如 NSString 是一个基础类,用于处理字符串。同时,语言也提供 @"string" 的语法方便的生成 NSString 对象。

ObjC 保留了 C 中的 printf 式的字符串操作形式,对比 C++ 重载移位操作符的形式,我想要更清爽一些。

对于 ObjC 对象,使用 %@ 来表示。给对象增加 description 方法就可以让处理函数知道该如何处理这个对象的 %@ 行为。