Objective-C 编程语言官网文档(十)-选择器

41 篇文章 0 订阅
14 篇文章 0 订阅

声明:本文档仅为个人学习过程中顺手翻译之作,方便开发的同胞借鉴参考。如有觉得译的不好不到位的地方,欢迎指正,将及时做出更正

尽量尊重原文档,因为首次Objective-C,有些地方可能直译了没有注意该语言的专有词,希望指正。如需转载,请注明出处


我的编程环境:

IDE:XCODE4.3.1

OS: MAC OS X 10.7.4

文章来译自:http://developer.apple.com/


选择器

Objective-C中选择器有两个含义。一种是用在代码中向对象发送消息时简单的代表了一个方法名。另一种是当源代码被编译时选择器会指向一个唯一标识以代替方法名,被编译后的选择器类型为 SEL。所有相同名字的方法会有相同的选择器。你可以使用一个选择器来调用一个对象的方法。这个是Cocoa中target-action设计模式的基础。(可以参考 target-action设计模式

方法和选择器

出于运行效率的考虑,在编译后的代码中不会使用由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” 


英文原文:点击打开链接


Selectors

In Objective-C, selector has two meanings. It can be used to refer simply to the name of a method when it’s used in a source-code message to an object. It also, though, refers to the unique identifier that replaces the name when the source code is compiled. Compiled selectors are of type SEL. All methods with the same name have the same selector. You can use a selector to invoke a method on an object—this provides the basis for the implementation of the target-action design pattern in Cocoa.

Methods and Selectors

For efficiency, full ASCII names are not used as method selectors in compiled code. Instead, the compiler writes each method name into a table, then pairs the name with a unique identifier that represents the method at runtime. The runtime system makes sure each identifier is unique: No two selectors are the same, and all methods with the same name have the same selector.

SEL and @selector

Compiled selectors are assigned to a special type, SEL, to distinguish them from other data. Valid selectors are never 0. You must let the system assign SEL identifiers to methods; it’s futile to assign them arbitrarily.

The @selector() directive lets you refer to the compiled selector, rather than to the full method name. Here, the selector for setWidth:height: is assigned to the setWidthHeight variable:

SEL setWidthHeight;
setWidthHeight = @selector(setWidth:height:);

It’s most efficient to assign values to SEL variables at compile time with the @selector() directive. However, in some cases, you may need to convert a character string to a selector at runtime. You can do this with theNSSelectorFromString function:

setWidthHeight = NSSelectorFromString(aBuffer);

Conversion in the opposite direction is also possible. The NSStringFromSelector function returns a method name for a selector:

NSString *method;
method = NSStringFromSelector(setWidthHeight);

Methods and Selectors

Compiled selectors identify method names, not method implementations. The display method for one class, for example, has the same selector as display methods defined in other classes. This is essential for polymorphism and dynamic binding; it lets you send the same message to receivers belonging to different classes. If there were one selector per method implementation, a message would be no different from a function call.

A class method and an instance method with the same name are assigned the same selector. However, because of their separate domains, there’s no confusion between the two. A class could define a display class method in addition to a display instance method.

Method Return and Parameter Types

The messaging routine has access to method implementations only through selectors, so it treats all methods with the same selector alike. It discovers the return type of a method, and the data types of its parameters, from the selector. Therefore, except for messages sent to statically typed receivers, dynamic binding requires all implementations of identically named methods to have the same return type and the same parameter types. (Statically typed receivers are an exception to this rule because the compiler can learn about the method implementation from the class type.)

Although identically named class methods and instance methods are represented by the same selector, they can have different parameter types and return types.

Varying the Message at Runtime

The performSelector:performSelector:withObject:, and performSelector:withObject:withObject:methods, defined in the NSObject protocol, take SEL identifiers as their initial parameters. All three methods map directly into the messaging function. For example,

[friend performSelector:@selector(gossipAbout:)
    withObject:aNeighbor];

is equivalent to:

[friend gossipAbout:aNeighbor];

These methods make it possible to vary a message at runtime, just as it’s possible to vary the object that receives the message. Variable names can be used in both halves of a message expression:

id   helper = getTheReceiver();
SEL  request = getTheSelector();
[helper performSelector:request];

In this example, the receiver (helper) is chosen at runtime (by the fictitious getTheReceiver function), and the method the receiver is asked to perform (request) is also determined at runtime (by the equally fictitiousgetTheSelector function).

Note: performSelector: and its companion methods return an object of type id. If the method that’s performed returns a different type, it should be cast to the proper type. (However, casting doesn’t work for all types; the method should return a pointer or a type compatible with a pointer.)

The Target-Action Design Pattern

In its treatment of user-interface controls, AppKit makes good use of the ability to vary both the receiver and the message at runtime.

NSControl objects are graphical devices that can be used to give instructions to an application. Most resemble real-world control devices such as buttons, switches, knobs, text fields, dials, menu items, and the like. In software, these devices stand between the application and the user. They interpret events coming from hardware devices such as the keyboard and mouse and translate them into application-specific instructions. For example, a button labeled “Find” would translate a mouse click into an instruction for the application to start searching for something.

AppKit defines a template for creating control devices and defines a few off-the-shelf devices of its own. For example, the NSButtonCell class defines an object that you can assign to an NSMatrix instance and initialize with a size, a label, a picture, a font, and a keyboard shortcut. When the user clicks the button (or uses the keyboard shortcut), the NSButtonCell object sends a message instructing the application to do something. To do this, an NSButtonCell object must be initialized not just with an image, a size, and a label, but with directions on what message to send and who to send it to. Accordingly, an NSButtonCell instance can be initialized for an action message (the method selector it should use in the message it sends) and a target (the object that should receive the message).

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

When the user clicks the corresponding button, the button cell sends the message using the NSObject protocol method performSelector:withObject:. All action messages take a single parameter, the id of the control device sending the message.

If Objective-C didn’t allow the message to be varied, all NSButtonCell objects would have to send the same message; the name of the method would be frozen in the NSButtonCell source code. Instead of simply implementing a mechanism for translating user actions into action messages, button cells and other controls would have to constrain the content of the message. Constrained messaging would make it difficult for any object to respond to more than one button cell. There would either have to be one target for each button, or the target object would have to discover which button the message came from and act accordingly. Each time you rearranged the user interface, you would also have to reimplement the method that responds to the action message. An absence of dynamic messaging would create an unnecessary complication that Objective-C happily avoids.

Avoiding Messaging Errors

If an object receives a message to perform a method that isn’t in its repertoire, an error results. It’s the same sort of error as calling a nonexistent function. But because messaging occurs at runtime, the error often isn’t evident until the program executes.

It’s relatively easy to avoid this error when the message selector is constant and the class of the receiving object is known. As you write your programs, you can make sure that the receiver is able to respond. If the receiver is statically typed, the compiler performs this test for you.

However, if the message selector or the class of the receiver varies, it may be necessary to postpone this test until runtime. The respondsToSelector: method, defined in the NSObject class, tests whether a receiver can respond to a message. It takes the method selector as a parameter and returns whether the receiver has access to a method matching the selector:

if ( [anObject respondsToSelector:@selector(setOrigin::)] )
    [anObject setOrigin:0.0 :0.0];
else
    fprintf(stderr, "%s can’t be placed\n",
        [NSStringFromClass([anObject class]) UTF8String]);

The respondsToSelector: runtime test is especially important when you send messages to objects that you don’t have control over at compile time. For example, if you write code that sends a message to an object represented by a variable that others can set, you should make sure the receiver implements a method that can respond to the message.

Note: An object can also arrange to have messages it receives forwarded to other objects if it doesn’t respond to them directly itself. In that case, from the caller’s perspective, the object appears to handle the message directly, even though it handles the message indirectly by forwarding it to another object. See “Message Forwarding” in Objective-C Runtime Programming Guide for more information.



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值