Objective-c下调用函数并不是采用的想C语言中的函数调用机制而是使用的一种消息传递的机制。下文将讨论这两种协议之间的区别。
传统的函数调用机制是在程序编译的阶段就已经将子函数的引用地址编译进了执行代码,所以当编译完成之后,函数名指向的就是函数的入口地址,而调用函数将直接导向至函数的入口地址,从而直接开始执行函数。
而Objective-c采用的是一种消息传递机制,即以消息的形式将要执行的函数传递给对应的对象,此时对象在自己的方法列表中查找对应的函数,如果找到了就执行,如果没有就在父类中查找。在这样的机制下,程序在编译的时候,要执行哪个函数其实是没有确定的,这也就是所谓的动态调用。其具体的操作如下。
在objective-c下,当编译器编译到一个函数调用的时候,编译器会调用一个obj_msgSend函数:
id objc_msgSend(id theReceiver, SELtheSelector, ...)
theReceiver是接收该消息的对象,SELtheSelector则是要调用函数的选择器(选择器的具体介绍详见下文),后面还可以跟多个函数传入的参数。在程序执行的时候,theReceiver对象会在自己的方法列表中找寻SELtheSelector指向的函数,如果没有找到就继续查找其父类,找到之后,执行该函数。以上就是objective-c下的消息机制。可以看出,在编译之后,具体要调用哪些函数并没有确定。
在消息传递函数obj_msgSend函数中有一个选择器,那么选择器是什么呢?其作用相当于函数指针,现在我看到的大多说用法都是在调用某些函数需要传递一个函数指针 参数时,使用@selector。它会在当前类里面查找selector后面所跟的函数,返回一个SEL类型的值。Objective-C在编译的时候,会根据方法的名字(包括参数序列),生成一个用 来区分这个方法的唯一的一个ID,这个ID就是SEL类型的。我们需要注意的是,只要方法的名字(包括参数序列)相同,那么它们的ID都是相同的。就是说,不管是超类还是子类,不管是有没有超类和子类的关系,只要名字相同那么ID就是一样的。除了函数名字和ID,编译器当然还要把方法编译成为机器可以执行的代码。其实简单地理解可以将SEL类型理解为一个char*,因为它的确可以用char*的形式打印出来:
SEL hah=@selector(message);
NSLog(@"%s",(char*)hah);
SEL类型的声明有多种方法,除了SEL=@selector(方法名)这种方式以外,还有另一种方法:
SEL 变量名 = NSSelectorFromString(方法名字的字符串);
而方法:
NSString *变量名 = NSStringFromSelector(SEL参数);
可以获得选择器对应方法的名称。
在Objective-c下,方法的定义如下:
typedef struct objc_method *Method;
typedef struct objc_ method {
SEL method_name;//方法名称
char *method_types;//方法参数类型
IMP method_imp;//方法实现的函数指针
};
typedef id (*IMP)(id, SEL, ...);
而id实际只是一个Class对象,其名字位isa,每一个NSObject对象都有一个isa成员: typedef struct objc_object {
Class isa;
} *id;
struct objc_class {
struct objc_class super_class; /*父类*/
const char *name; /*类名字*/
long version; /*版本信息*/
long info; /*类信息*/
long instance_size; /*实例大小*/
struct objc_ivar_list *ivars; /*实例参数链表*/
struct objc_method_list **methodLists; /*方法链表*/
struct objc_cache *cache; /*方法缓存*/
struct objc_protocol_list *protocols; /*协议链表*/
};
到这里一切都明了了,这个IMP对象实际上指向的是函数的执行地址,同时还传入了一个接收对象的id(self指针)以及对应SEL和对应的参数。同时还会返回一个id。
也许你会问,这样的机制相对于以前的函数调用机制来说有什么优势呢?在我看来,最重要的就是其实现了函数的动态调用,我们不需要在一开始编写代码的时候就决定在某个特定的时候需要执行什么函数。而是可以在之后的某个时候再决定要执行的函数。看下面这个实例:
NSMutableArray *list2=[NSMutableArray arrayWithObjects:@"four",@"five",@"six",nil];
SEL sel=NSSelectorFromString(@"count");
NSLog(@"items of list2:%@ \n it has %lu elements",list2,[list2 performSelector:sel]);
只需要在一个特定的时候传入函数的名称,我们就可以动态调用函数。这就是该机制的优势。