在入门级别的ObjC 教程中,我们常对从C++或Java 或其他面向对象语言转过来的程序员说,ObjC 中的方法调用(ObjC中的术语为消息)跟其他语言中的方法调用差不多,只是形式有些不同而已。
譬如C++ 中的:
Bird * aBird = new Bird();
aBird->fly();
在ObjC 中则如下:
Bird * aBird = [[Bird alloc] init];
[aBird fly];
初看起来,好像只是书写形式不同而已,实则差异大矣。C++中的方法调用可能是动态的,也可能是静态的;而ObjC中的消息都为动态的。下文将详细介绍为什么是动态的,以及编译器在这背后做了些什么事情。
class
1 struct objc_class { 2 Class isa OBJC_ISA_AVAILABILITY; 3 4 #if !__OBJC2__ 5 Class super_class OBJC2_UNAVAILABLE; 6 const char *name OBJC2_UNAVAILABLE; 7 long version OBJC2_UNAVAILABLE; 8 long info OBJC2_UNAVAILABLE; 9 long instance_size OBJC2_UNAVAILABLE; 10 struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; 11 struct objc_method_list **methodLists OBJC2_UNAVAILABLE; 12 struct objc_cache *cache OBJC2_UNAVAILABLE; 13 struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; 14 #endif 15 16 } OBJC2_UNAVAILABLE; 17 /* Use `Class` instead of `struct objc_class *` */
由此可见,Class 是指向类结构体的指针,该类结构体含有一个指向其父类类结构的指针,该类方法的链表,该类方法的缓存以及其他必要信息。
NSObject 的class 方法就返回这样一个指向其类结构的指针。每一个类实例对象的第一个实例变量是一个指向该对象的类结构的指针,叫做isa。通过该指针,对象可以访问它对应的类以及相应的父类。如图一所示:
如图一所示,圆形所代表的实例对象的第一个实例变量为 isa,它指向该类的类结构 The object’s class。而该类结构有一个指向其父类类结构的指针superclass, 以及自身消息名称(selector)/实现地址(address)的方法链表。
方法的含义:
注意这里所说的方法链表里面存储的是Method 类型的。图一中selector 就是指 Method的 SEL, address就是指Method的 IMP。 Method 在头文件 objc_class.h中定义如下:
typedef struct objc_method *Method;
typedef struct objc_ method {
SEL method_name;
char *method_types;
IMP method_imp;
};
一个方法 Method,其包含一个方法选标 SEL – 表示该方法的名称,一个types – 表示该方法参数的类型,一个 IMP - 指向该方法的具体实现的函数指针。
SEL 的含义:
在前面我们看到方法选标 SEL 的定义为:
typedef struct objc_selector *SEL;
它是一个指向 objc_selector 指针,表示方法的名字/签名。如下所示,打印出 selector。
-(NSInteger)maxIn:(NSInteger)a theOther:(NSInteger)b
{
return (a > b) ? a : b;
}
NSLog(@"SEL=%s", @selector(maxIn:theOther:));
输出:SEL=maxIn:theOther:
不同的类可以拥有相同的 selector,这个没有问题,因为不同类的实例对象performSelector相同的 selector 时,会在各自的消息选标(selector)/实现地址(address) 方法链表中根据 selector 去查找具体的方法实现IMP, 然后用这个方法实现去执行具体的实现代码。这是一个动态绑定的过程,在编译的时候,我们不知道最终会执行哪一些代码,只有在执行的时候,通过selector去查询,我们才能确定具体的执行代码。
IMP 的含义:
在前面我们也看到 IMP 的定义为:
typedef id (*IMP)(id, SEL, ...);
根据前面id 的定义,我们知道 id是一个指向 objc_object 结构体的指针,该结构体只有一个成员isa,所以任何继承自 NSObject 的类对象都可以用id 来指代,因为 NSObject 的第一个成员实例就是isa。
至此,我们就很清楚地知道 IMP 的含义:IMP 是一个函数指针,这个被指向的函数包含一个接收消息的对象id(self 指针), 调用方法的选标 SEL (方法名),以及不定个数的方法参数,并返回一个id。也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码 。我们可以像在C语言里面一样使用这个函数指针。
NSObject 类中的methodForSelector:方法就是这样一个获取指向方法实现IMP 的指针,methodForSelector:返回的指针和赋值的变量类型必须完全一致,包括方法的参数类型和返回值类型。
下面的例子展示了怎么使用指针来调用setFilled:的方法实现:
void (*setter)(id, SEL, BOOL);
int i;
setter = (void(*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
for (i = 0; i < 1000; i++)
setter(targetList[i], @selector(setFilled:), YES);
使用methodForSelector:来避免动态绑定将减少大部分消息的开销,但是这只有在指定的消息被重复发送很多次时才有意义,例如上面的for循环。
注意,methodForSelector:是Cocoa运行时系统的提供的功能,而不是Objective-C语言本身的功能。