小别离朵朵家的床:Objective-C选择器 Selector

来源:百度文库 编辑:中财网 时间:2024/05/05 05:40:52

Objective-C选择器 Selector

5.4 SEL类型 

1     id cattle[3]; 
2     SEL say; 
3     SEL skin; 

其中id cattle[3]定义了一个数组用于存储Cattle或者Bull对象。这一行代码估计大家都很熟悉,笔者就不赘述了。像这样的传统的数组并不能完全满足我们的需求,当我们需要做诸如追加,删除等操作的时候,会很不方便。在随后的章节里面笔者将要向大家介绍传统数组的替代解决方案NSArray。 

上一段代码的第二行和第三行是本节所关注的,就是SEL类型。Objective-C在编译的时候,会根据方法的名字(包括参数序列),生成一个用 来区分这个方法的唯一的一个ID,这个ID就是SEL类型的。我们需要注意的是,只要方法的名字(包括参数序列)相同,那么它们的ID都是相同的。就是 说,不管是超类还是子类,不管是有没有超类和子类的关系,只要名字相同那么ID就是一样的。除了函数名字和ID,编译器当然还要把方法编译成为机器可以执 行的代码,这样,在一个编译好的类里面,就产生了如下图所示方法的表格示意图(本构造属于笔者推测,没有得到官方证实,所以图5-2为示意图仅供参考,我们可以暂时认为是这样的)。 

 

图5-2,方法的表格示意图 

请注意setSkinColor后面有一个冒号,因为它是带参数的。由于存在这样的一个表格,所以在程序执行的时候,我们可以方便的通过方法的名字,获取到方法的ID也就是我们所说的SEL,反之亦然。具体的使用方法如下: 


1     SEL 变量名 = @selector(方法名字); 
2     SEL 变量名 = NSSelectorFromString(方法名字的字符串); 
3     NSString *变量名 = NSStringFromSelector(SEL参数); 


其中第1行是直接在程序里面写上方法的名字,第2行是写上方法名字的字符串,第3行是通过SEL变量获得方法的名字。我们得到了SEL变量之后,可以通过下面的调用来给一个对象发送消息: 

[对象 performSelector:SEL变量 withObject:参数1 withObject:参数2]; 

这样的机制大大的增加了我们的程序的灵活性,我们可以通过给一个方法传递SEL参数,让这个方法动态的执行某一个方法;我们也可以通过配置文件指定需要执行的方法,程序读取配置文件之后把方法的字符串翻译成为SEL变量然后给相应的对象发送这个消息。 

从效率的角度上来说,执行的时候不是通过方法名字而是方法ID也就是一个整数来查找方法,由于整数的查找和匹配比字符串要快得多,所以这样可以在某种程度上提高执行的效率。 


下面再来看看苹果官方文档对于消息机制的说明:

objc_msgSend函数

 在Objective-C中,消息是直到运行的时候才和方法实现绑定的。编译器会把一个消息表达式, [receiver message]转换成一个对消息函数objc_msgSend的调用。该函数有两个主要参数:消息接收者和消息对应的方法名字——也就是方法选标: objc_msgSend(receiver, selector)同时接收消息中的任意数目的参数: objc_msgSend(receiver, selector, arg1, arg2, ...)该消息函数做了动态绑定所需要的一切: 它首先找到选标所对应的方法实现。因为不同的类对同一方法可能会有不同的实现,所以找到的方法实现依赖于消息接收者的类型。然后将消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传给找到的方法实现。最后,将方法实现的返回值作为该函数的返回值返回。注意:编译器将自动插入调用该消息函数的代码。您无须在代码中显示调用该消息函数。消息机制的关键在于编译器为类和对象生成的结构。每个类的结构中至少包括两个基本元素: 指向父类的指针。类的方法表。方法表将方法选标和该类的方法实现的地址关联起来。例如,setOrigin::的方法选标和setOrigin::的方法实现的地址关联,display 的方法选标和display的方法实现的地址关联,等等。当新的对象被创建时,其内存同时被分配,实例变量也同时被初始化。对象的第一个实例变量是一个指向该对象的类结构的指针,叫做isa。通过该指针,对象可以访问它对应的类以及相应的父类。 注意:尽管严格来说这并不是Obective-C语言的一部分,但是在Objective-C运行时系统中对象需要有isa指针。对象和结构体struct objc_object(在objc/objc.h中定义)必须“一致”。然而,您很少需要创建您自己的根对象,因为从NSObject或者NSProxy继承的对象都自动包括isa变量。   当对象收到消息时,消息函数首先根据该对象的isa指针找到该对象所对应的类的方法表,并从表中寻找该消息对应的方法选标。如果找不到,objc_msgSend将继续从父类中寻找,直到NSObject类。一旦找到了方法选标, objc_msgSend则以消息接收者对象为参数调用,调用该选标对应的方法实现。 这就是在运行时系统中选择方法实现的方式。在面向对象编程中,一般称作方法和消息动态绑定的过程。 为了加快消息的处理过程,运行时系统通常会将使用过的方法选标和方法实现的地址放入缓存中。每个类都有一个独立的缓存,同时包括继承的方法和在该类中定义的方法。消息函数会首先检查消息接收者对象对应的类的缓存(理论上,如果一个方法被使用过一次,那么它很可能被再次使用)。如果在缓存中已经有了需要的方法选标,则消息仅仅比函数调用慢一点点。如果程序运行了足够长的时间,几乎每个消息都能在缓存中找到方法实现。程序运行时,缓存也将随着新的消息的增加而增加。

注:方法选标 即SEL 类型值 也就是@selector(methodname)获取的一个结构变量


下面是我从cocoachina.com上下载的关于objc_msgSend()函数的用法代码(省去了头文件)

//test.m

////  test.m//  SelectorTest////  Created by Zenny Chen on 09-7-20.//  Copyright 2009 GreenGames Studio. All rights reserved.// #import "test.h" static NSString *test1Str = @"I\'m No.1";static NSString *test2Str = @"I\'m No.2";  @implementation Test1 - (id)init{    value = 0;    return self;} - (void)setValue : (NSInteger)val{    value = val;} - (NSInteger)getValue{    return value;}  + (void)setValue : (NSString*)str{    if(str != nil)        test1Str = [test1Str stringByAppendingString:[@"\n" stringByAppendingString:str]];} + (NSString*)getValue{    return test1Str;}  @end  @implementation Test2 - (id)init{    value = 0;    return self;} - (void)setValue : (NSInteger)val{    value = val;} - (NSInteger)getValue{    return value;} + (void)setValue : (NSString*)str{    if(str != nil)        test2Str = [test2Str stringByAppendingString:[@"\n" stringByAppendingString:str]];} + (NSString*)getValue{    return test2Str;} @end



//SelectorTest.m
 #import #import "test.h"#import "runtime.h"  int main (int argc, const char * argv[]) {    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];     // insert code here...     SEL selSetter = @selector(setValue:);    SEL selGetter = @selector(getValue);    NSLog(@"The setter method name is: %@", NSStringFromSelector(selSetter));    NSLog(@"The getter method name is: %@", NSStringFromSelector(selGetter));     // Send a message to an instance    Test1 *t1 = [[Test1 alloc] init];    objc_msgSend(t1, selSetter, 100);                           // equal to [t1 setValue:100];    NSLog(@"The t1 value is: %d", objc_msgSend(t1, selGetter)); // equal to [t1 getValue];        Test2 *t2 = [[Test2 alloc] init];    objc_msgSend(t2, selSetter, -100);    NSLog(@"The t2 value is: %d", objc_msgSend(t2, selGetter));        // Send a message to a class    objc_msgSend(objc_lookUpClass("Test1"), selSetter, @"Ah, yes!");    // equal to [Test1 setValue:@"Ah, yes!"];    objc_msgSend(objc_lookUpClass("Test2"), selSetter, @"Perfect!");    NSLog(@"The Test1 result is: %@", objc_msgSend(objc_lookUpClass("Test1"), selGetter));    NSLog(@"The Test2 result is: %@", objc_msgSend(objc_lookUpClass("Test2"), selGetter));     // Other runtime APIs    NSLog(@"The class name is: %s", class_getName(((id)t1)->isa));        Method test1Method = class_getClassMethod(object_getClass(t1), selSetter);    NSLog(@"The method name is: %@", NSStringFromSelector(method_getName(test1Method)));        //现在使用objective-C/C++方法来处理选择符    [t1 performSelector:selSetter withObject:(id)111];  //注意,这里不能使用[NSNumber numberWithInteger:111]    [t2 performSelector:selSetter withObject:(id)222];    [Test1 performSelector:selSetter withObject:@"Mama Miya!"];    [Test2 performSelector:selSetter withObject:@"That's what I needed"];        NSLog(@"The t1 value now is: %d", [t1 performSelector:selGetter]);    NSLog(@"The t2 value now is: %d", [t2 performSelector:selGetter]);    NSLog(@"Test1 value is: %@", [Test1 performSelector:selGetter]);    NSLog(@"Test2 value is: %@", [Test2 performSelector:selGetter]);    //注意:(id)performSelector只有三种形式:无参数、带有一个参数、带有两个参数,并且参数类型均为id类型。    //因此,若当所需参数多时可以使用数组传入,如果多个参数类型又不同,那么要么改进设计方法,要么使用运行时API进行处理。(可以传递自己设计的对象)        [t1 release];    [pool drain];    return 0;} // please refer to the runtime refernece manual from Apple inc.// The runtime APIs referred to here: objc_msgSend, objc_lookUpClass, class_getName, method_getName, class_getClassMethod