9.5 有关类的问题
开始使用可以包含来自不同类的对象的变量时,可能会遇到以下问题:
·这个对象是矩形吗?
·这个对象支持print方法吗?
·这个对象是Graphics类或是其子类的成员吗?
这些问题的答案可以用来执行不同的代码序列,避免错误或在运行程序时检查程序的完整性。
表9-1总结了NSObject类所支持的一些基本方法,它们用来提出这类问题。在这个表中,class-object是一个类对象(通常是由class方法产生的),selector是一个SEL类型的值(通常是由@selector指令产生的)。
这里没有描述其他可用的方法,这些方法允许你提出关于是否符合某项协议的问题(协议将在第11章中讲述),还有一些方法允许你提出有关动态解析方法(本文中不讨论)的问题。
要根据类名或另一个对象生成一个类对象,可以向它发送class消息。所以,要从名为Square的类中获得类对象,可以编写如下代码:
[Square class]
如果mySquare是Square对象的实例,可以通过如下代码知道它所属的类:
[mySquare class]
要查看存储在变量obj1和obj2中的对象是不是相同的类实例,可以编写如下的代码:
if([obj1 class]==[obj2 class])
……
要查看变量myFract是不是Fraction类的实例,可如下测试表达式的结果:
[myFract isMemberOf:[Fraction class]]
要生成表9-1中列出的所谓的selector,可以对一个方法名应用@selector指令。例如,
@selector(alloc)
为名为alloc的方法生成一个SEL类型的值,你知道这个方法是从NSObject类继承的。表达式
@selector(setTo:over:)
为setTo:over:方法生成一个selector,这个setTo:over:方法是在Fraction类中实现的(切记方法名称中的冒号字符)。
要查看Fraction类的实例是否响应setTo:over方法,可以如下测试表达式的返回值:
[Fraction instancesRespondToSelector:@selector(setTo:over:)]
记住,测试包括继承的方法,并不是只测试直接定义在类中的方法。
performSelector:方法和它的变形版本(在表9-1中没有显示)允许你向对象发送消息,这条消息可以是存储在变量中的selector。例如,考虑以下代码序列:
SEL action;
id graphicObject;
……
action=@selector(draw);
……
[graphicObject performSelector:action];
在这个例子中,SEL变量action所指定的方法被发送到存储在grphicObject中的任何图形对象。根据推测,即使已经把这个行为指定为draw,在程序执行时,行为也可能发生变化,可能依赖于用户的输入。要先确定对象是否可以响应这个动作,可能使用以下方式:
if([graphicObject respondsToSelector:action]==YES)
[graphicObject perform:action]
else
//error handling code here
注意还可能由于重载doesNotRecognize:方法而捕获错误。只要向一个类发送一条未识别的消息并传递未识别的selector作为其参数,就会调用这个方法。
还可以采用其他策略:可以使用forward:方法将消息转发给其他人去处理,或者试着发送方法并捕获发生的异常。稍后将简要介绍这个技术。
代码清单9-3对在第8章“继承”中定义的Square和Rectangle类提出了一些问题。在查看实际输出之前,尝试预测程序的结果(现在可不能偷看!)。
代码清单9-3
import“Square.h”
int main(int argc, char*argv[])
{
NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];
Square*mySquare=[[Square alloc]init];
//isMemberOf:
if([mySquare isMemberOfClass:[Square class]]==YES)
NSLog(@“mySquare is a member of Square class”);
if([mySquare isMemberOfClass:[Rectangle class]]==YES)
NSLog(@“mySquare is a member of Rectangle class”);
if([mySquare isMemberOfClass:[NSObject class]]==YES)
NSLog(@“mySquare is a member of NSObject class”);
//isKindOf:
if([mySquare isKindOfClass:[Square class]]==YES)
NSLog(@“mySquare is a kind of Square”);
if([mySquare isKindOfClass:[Rectangle class]]==YES)
NSLog(@“mySquare is a kind of Rectangle”);
if([mySquare isKindOfClass:[NSObject class]]==YES)
NSLog(@“mySquare is a kind of NSObject”);
//respondsTo:
if([mySquare respondsToSelector:@selector(setSide:)]==YES)
NSLog(@“mySquare responds to setSide:method”);
if([mySquare respondsToSelector:@selector(setWidth:andHeight:)]==YES)
NSLog(@“mySquare responds to setWidth:andHeight:method”);
if([Square respondsToSelector:@selector(alloc)]==YES)
NSLog(@“Square class responds to alloc method”);
//instancesRespondTo:
if([Rectangle instancesRespondToSelector:@selector(setSide:)]==YES)
NSLog(@“Instances of Rectangle respond to setSide:method”);
if([Square instancesRespondToSelector:@selector(setSide:)]==YES)
NSLog(@“Instances of Square respond to setSide:method”);
if([Square isSubclassOfClass:[Rectangle class]]==YES)
NSLog(@“Square is a subclass of a rectangle”);
[mySquare release];
[pool drain];
return 0;
}
一定要使用Square、Rectangle和XYPoint类(它们都出现在第8章“继承”中)的实现文件编译这个程序。
代码清单9-3输出
mySquare is a member of Square class
mySquare is a kind of Square
mySquare is a kind of Rectangle
mySquare is a kind of NSObject
mySquare responds to setSide:method
mySquare responds to setWidth:andHeight:method
Square class responds to alloc method
Instances of Square respond to setSide:method
Square is a subclass of a rectangle
代码清单9-3的输出应该很清晰。记住isMemberOf:测试类中的直接成员关系,而isKindOf:检测继承层次中的关系。所以mySquare是Square类的成员;但是它同样是“某种”Square、Rectangle和NSObject,因为它存在于这些类的层次结构中(很明显,所有对象都应该在有关NSObject类的isKindOf:的测试时返回YES,除非定义了新的根对象)。
测试语句
if([Square respondsTo:@selector(alloc)]==YES)
检测Square类是否响应alloc类方法,确实响应,因为Square类是从根对象NSObject继承来的。
现在你认识到总能在消息表达式中直接使用类名作为接收者,并且在以前的表达式中不必编写
[Square class]
不过如果你想编写,也可以编写。这是唯一可以这样做的地方。在其他地方就需要应用class的方法来获取类对象。