Objective-C选择器

选择器

Objective-C中选择器有两个含义

1、 向一个对象发送消息时简单的代表了一个方法名

2、 当源代码编译时选择器会被指向一个唯一标识以代替方法名

 

方法名和唯一标识的关系

处于运行效率的考虑,在编译后的代码中不会使用有ASCII码组成的方法名,

取而代之的是,编译器会将每个方法名写到一张表去,然后为每个方法名分

配一个唯一标识用于在运行时标识一个方法

 

运行时系统会确保每个标识都是唯一的,不会出现两个相同的选择器,并且

所有相同名称的方法都使用相同的选择器

 

SEL@selector

为了和其他数据区分开,编译后的选择器会赋给一个SEL类型的变量,选择

器永远都不会是0,你必须让系统来为选择器分配SEL标识符,重新分配是

无效的

 

@selector()指令让你可以直接引用编译后的选择器而不是方法名,下面例子

中,将选择器setWidth:height:分配给变量setWidthHeight

SEL setWidthHeight;

setWidthHeight = @selector(setWidth:height:);

 

在编译时使用@selector()指令将选择器分配给SEL是最有效率的,但是在某

些情况下,你需要在运行时将一个字符串转化为一个选择器。你可以使用

NSSelectorFromString方法来实现

setWidthHeight = NSSelectorFromString(aBuffer);

相反还可以从选择器得到方法名。NSStringFromSelector方法为一个选择器

返回方法名

NSString *method;

method = NSStringFromSelector(setWidthHeight);

方法和选择器

编译后的选择器标识的是方法名而不是方法本身的实现。例如,一个类有一个方法 display ,它和其他类中定义的方法 display 使用同一个选择器。这是动态绑定和多态性的本质;这样可以使你可以向不同类型的接收器发送相同的消息。如果每一个方法都有一个选择器,那么消息机制就和方法调用完全相同了。

一个类方法和一个实例方法如果名字相同他们的选择器也相同。但是由于他们的作用域不同他们之间并不会造成混淆。一个类可以定义了一个 display 实例方法的同时再定义一个 display 类方法。

方法的返回值和参数类型

消息只有通过选择器才能访问一个方法的实现,所以它对拥有相同的选择器的方法的处理都是类似的。它会从选择器中找到方法的参数类型和返回值类型。因此,除非向一个静态类型的接收器发送消息,而动态绑定需要所有相同名字的方法的返回值类型和参数类型全部相同。(静态类型的接收器是这个规则的一个特例,因为编译器可以通过类的类型获得该方法实现)

尽管同名的类方法和实例方法拥有相同的选择器,但它们可以有不同的参数类型和返回值类型。

运行时改变消息

方法 performSelector:performSelector:withObject: ,  performSelector:withObject:withObject:是定义在 NSObject 协议中的一组方法,都使用 SEL 标示符作为它们的初始化参数。这三个方法都可以直接映射为消息功能。例如:

[friend performSelector:@selector(gossipAbout:)

    withObject:aNeighbor];

等价于:

[friend gossipAbout:aNeighbor];

这些方法使得在运行时改变消息变为可能,就像可以改变接收消息的对象一样。变量名在消息表达式的两个部分都可以使用:

id   helper = getTheReceiver();

SEL  request = getTheSelector();

[helper performSelector:request];

在这个例子中,接收器 (helper) 是在运行时被选择的(通过一个虚拟的函数 getTheReceiver ),同时接收器请求执行的方法(request)也是在运行时确定的(同样也是通过一个虚拟方法 getTheSelector)

注意 performSelector:以及它相关的方法返回一个id类型的对象。如果调用的方法返回的值是其他类型可以被转化为合适的类型。(但是不是所有的类型都能转化;需要方法返回的是一个指针或者和指针类型相符合的类型。)

注意: performSelector:以及它相关的方法返回一个 id 类型的对象。如果调用的方法返回的值是一个不同的类型,那么它应当被转化为合适的类型。(但是不是所有的类型都能转化;方法应该返回的是一个指针或者和指针类型相符合/兼容的类型。)

 

Target-Action 设计模式

在用户界面控制的处理中,AppKit很好的利用了在运行时改变接收器和消息的能力。

NSControl 对象可以用于由图形设备为应用发送指令。大多类似真实环境下的控制设备,例如:按钮,开关,把手,文本输入框,图标,菜单项等等。在软件中,它们介于应用与用户之间,他们负责将诸如键盘、鼠标等硬件设备所发出的指令转换为应用特有的指令。例如,一个叫“Find”的按钮会将一个鼠标点击事件转换为一个检索指令发送给应用以开始一次检索。

AppKit定义了一个模板用于创建控制器同时也定义好了一些现成的控制器。例如, NSButtonCell 类定义了一个对象,你可以把它分配给一个 NSMatrix 实例并为它初始化大小,定义它的标题、图片、字体、快捷键。当用户点击一个按钮(或使用键盘上的快捷键), NSButtonCell 对象会发出一个消息指令给应用,告诉应用做点什么。要做到这些, NSButtonCell 对象不仅仅要初始化图片,设置大小,标题,而且要指导用什么消息发送以及消息发给谁。相应的,一个 NSButtonCell 实例可以初始化为一个动作消息(发送的消息中需要调用的方法)和一个目标(消息的接收者)。

[myButtonCell setAction:@selector(reapTheWind:)];

[myButtonCell setTarget:anObject];

当用户点击了相应的按钮,按钮就会使用 NSObject 协议中的方法performSelector:withObject:发送消息。所有的动作类消息都有一个参数,发送消息的控制器的 id 

如果 Objective-C 不允许消息变化,那么所有 NSButtonCell 对象只能发送相同的消息;方法名都只能写死在  NSButtonCel 源码中。这样我们就不能像现在这样用一个简单的机制将用户动作转化为一个消息,而是所有按钮和其他控制器都需要限制消息的具体内容。这种被限制的消息会使得一个对象想要响应多个按钮时困难重重。这样就需要为每个按钮创建一个目标或者目标对象,需要判断是哪个按钮发送的消息并做出相应的反应。并且每次当你调整了用户界面都必须修改响应这些动作的方法。如果没有了动态消息会造成许多Objective-C所乐于避免的麻烦。

避免消息错误

要是一个对象收到一个消息要执行一个它未定义的方法,那么会产生一个错误。这和调用一个不存在的函数有些相似。但是由于发送消息是在运行时进行的,所以这些错误经常只有到程序实际运行时才会暴露出来。

当消息的选择器是一个常量并且知道接收者的类时这种错误相对容易避免。例如你自己在写程序时,你当然能够确定接收者能够响应什么。如果接收者是静态类型,编译器会替你完成校验。

但是,如果消息选择器或者接收者的类是可变的,这种校验就只能到运行时才能进行。 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” 

 

posted @ 2012-08-13 11:39  linqianqiu  阅读(397)  评论(0编辑  收藏  举报