本文档版权归NickTang所有,没有本人书面或电子邮件允许,不许转载,摘录,发表。多谢!
在Objective-C语言中, 选择器(selector) 有两层含义,在源代码中,它指代一个函数名称,在编译期间,它被一个唯一的标记符替代。编译后的选择器被替换成一个SEL类型
. 所有具有同一个名称的函数具有同一个选择器。你可以使用选择器调用一个对象的方法--这是Cocoa中的目标-动作设计模式的最基本实现方式。
函数和选择器
为了效率,编译后的代码不再使用函数名称。作为替代,编译器把所有的函数名称保存在一个表中,然后为每一个函数名称声称一个唯一的标识,并在运行的时候使用这个标识作为函数的替代。运行环境包装每一个标识是唯一的:同样名称的函数具有相同的选择器,不同的函数的选择器绝对不同。
SEL和@selector
编译后的选择器使用一个特别的类型,SEL,以区别与其他数据类型。你必须使用一个函数来付值给一个
SEL变量。
@selector()指示在编译期间的选择器。下面的例子演示了使用函数
setWidth:height:来付值给SEL类型的变量
setWidthHeight
:
SEL setWidthHeight; |
setWidthHeight = @selector(setWidth:height:); |
在编译期间,使用@selector()来直接设置SEL变量是很有效的。但是,有些情况下,你可能需要在运行期间使用一个字符串来转换成一个选择器,这个时候,你需要使用
NSSelectorFromString
函数:
setWidthHeight = NSSelectorFromString(aBuffer); |
相反方向的转换也是有可能需要的。函数 NSStringFromSelector就是做这个事情的:
NSString *method; |
method = NSStringFromSelector(setWidthHeight); |
函数和选择器
编译后的选择器指代函数的名称,而不是函数的实现。例如,一个类中的displa函数在其他类中可能有一个相同名字的函数,那么这就是多态性和动态绑定的本质;它可以让你对不同的类发送相同的消息。如果每一个函数的实现都不同,那么一个消息可能会有不同的函数调用。
具有相同名称的类函数和实例函数具有相同的选择器。但是,由于它们在不同的作用域中,所以这两种情况不会找出冲突,一个类可以拥有一个display实例函数的同时拥有一个display类函数。
函数的返回类型和参数类型
消息必须通过选择器才能转化为对函数实现的调用,因此一个消息对所有的相应的函数都会作为相同的选择器使用。消息通过选择器来了解函数的参数类型和返回类型。故此,除了是接受者是静态指定的,动态绑定需要所有的同一个名字的函数必须具有相同的函数类型和返回类型。(由于编译器会对静态接受者进行检查,所以它是这个规则的一个例外)
虽然相同的类方法和实例方法是有同一个选择器,但是它们可以有不同的参数类型和返回类型。
运行期验证消息
定义在类NSObject中的函数performSelector:,
performSelector:withObject:和
performSelector:withObject:withObject:
使用一个SEL作为参数。这三个函数最后都转换为相应的函数调用,例如:
[friend performSelector:@selector(gossipAbout:) |
withObject:aNeighbor]; |
和下面的代码相等:
[friend gossipAbout:aNeighbor]; |
这三个函数使得在运行期对消息和消息接受者验证称为可能,并且在消息的任何部分都可以使用变量:
id helper = getTheReceiver(); |
SEL request = getTheSelector(); |
[helper performSelector:request]; |
在上面的例子中,接收者(helper
)是在运行期决定的 (通过函数getTheReceiver调用决定
), 并且接受者的函数(request
) 也是通过运行期决定的 。
注: performSelector:等一干函数返回一个
id类型的变量。如果函数返回一个其他的类型,必须通过转换来变成合适的方式(当然,不是所有的类型都能被转换,函数应该返回一个指针或者相似的类型)。
避免消息错误
如果一个接受者接到一个不在函数列表中的消息,就会发生一个错误。这个错误和调用一个不存在的函数的错误一样。不过由于消息是在运行期发生的,所以这个错误直到程序运行的时候才会发生发生。
这个错误在接受者和消息都是固定的时候是很容易避免的。由于是你写的程序,所以你可以保证接受者是能够对消息做出响应的。如果接受者是固定的,那么编译器可以为你做这样的检测的。
不过,如果消息接受者和消息本身是动态的,那么这个检验工作就要推迟到运行期了。定义在NSObject类中的函数respondsToSelector:就是为了测试一个接受者是否响应某个消息。它接受一个函数选择器作为参数,返回接受者能否响应这个选择器:
if ( [anObject respondsToSelector:@selector(setOrigin::)] ) |
[anObject setOrigin:0.0 :0.0]; |
else |
fprintf(stderr, "%s can’t be placed\n", |
[NSStringFromClass([anObject class]) UTF8String]); |
respondsToSelector:所作的运行期检测是很重要的,特别是你发消息到不是你所控制的类中的时候。例如,你发送一个消息到一个可以通过变量设置的接受者得时候,你必须通过这个函数来确认接受者是否实现了这个函数。
注: 一个对象可以前转一个消息到其他的接受者,对于消息的发送方看到的是这个对象处理了这个消息。前参考Objective-C Runtime Programming Guide“中的 “Message Forwarding”