向self以及super发送消息
Objectivec-C中提供了两个关键字self和super。在类的方法中我们可以使用这两关键字来指明消息的接收者。假设,我们定义了一个reposition方法用于修改其作用对象的坐标。这个方法中调用了setOrigin::方法来完成这个功能。所需要做的就是在reposition方法中向reposition消息的接收者发送setOrigin消息。这样以来,在reposition方法的代码中我们就可以使用self或者super关键字。如下:
- reposition
{
...
[self setOrigin:someX :someY];
...
}
或者是:
- reposition
{
...
[super setOrigin:someX :someY];
...
}
其中的self和super都指的是reposition消息的接收者。这个接收者可能是任何对象。然而这样过关键字是有很大区别的。self关键字是根据消息调用方法时传入到所有方法中的一个隐藏的参数。在方法的实现中是可以自由使用的,就像使用类中的实例变量一样。只有在消息表达式中作为接收者的时候才可以使用super,此时super相当于是self的替代品。作为消息接收者时,两者的只要区别在于消息的处理过程不同:● self表示按照正常的方式来搜索对应的方法。也就是说self表示从消息接收者的类的分发表(dispatch table)中开始查找对应的方法。以上面的例子来说,也就是从接收reposition消息的对象对应的类的分发表中开始搜素对应的方法。
● super则表示从出现该super关键字的类的超类的分发表开始查找对应的方法。以上面的例子来说,也就是从定义reposition方法的类的超类的分发表开始搜素对应的方法。
当向super发送消息的时候,编译器是使用另外一个发送消息的例行程序段来代替objc_msgSend函数。该程序段直接定向到定义该类的超类,即定向到向super发送消息的类的超类,而不是接收到该消息的对象的类。
一个使用self和super的实例程序
下面的程序中使用到了三个有继承关系的类,此时self和super的差别会很清楚。程序中创建了一个Low类的对象。Low的直接超类是Mid类;而Mid类的直接超类是High。三个类中都定义了negotiate方法,三个类分别按照自己的需要实现这个方法。另外,Mid类中定义了功能上比较含糊的一个方法(该方法仅供该演示程序使用):makeLastingPeace,其中调用了negotiate方法。这三个类定义如图2-2所示:
┌───────────────┐
│ superclass │
High │ │<───────┐
│ -negotiate │ │
│ │ │
└───────────────┘ │
│
┌───────────────────┐ │
│ superclass │────────┘
│ │
Mid │ -negotiate │
│ -makeLastingPeace │<───────┐
└───────────────────┘ │
│
│
┌────────────────┐ │
│ superclass │────────┘
Low │ │
│ -negotiate │
│ │
└────────────────┘
假定上面的Mid类中的makeLastingPeace方法的实现如下,
- makeLastingPeace
{
[self negotiate];
...
}
其中使用到了self来表示negotiate消息的接收者。当给一个Low类的对象发送消息从而执行makeLastingPeace方法的时候,makeLastingPeace方法会发送negotiate消息给该Low类的对象。发送消息的例行程序段会找到Low类中定义的negotiate方法,也就是self对应的类。
然而,如果makeLastingPeace方法的实现如下,其中使用到的是super而不是self:
- makeLastingPeace
{
[super negotiate];
...
}
发送消息的例行程序段最终找到的是在High类中定义的negotiate方法。此时发送消息的例行程序段会忽略接收makeLastingPeace消息的对象对应的类Low,并且越过定义makeLastingPeace方法的类Mid。可见不管是使用了self还是super关键字,最终都不会调用Mid类中的negotiate方法的。正如这是示例程序展示的那样,super关键字提供了一种机制:绕过重写了超类方法的方法。示例中的super关键字就使得程序绕过了Mid类中定义的negotiate方法,而执行其超类High类中的negotiate方法。
这种不能使用Mid类中noegotiate方法的机制貌似是一种缺陷,但是在特定的情形下,是必须这样做的:
● Low类的作者有意重载了Mid类中的negotiate方法,以便达到Low类的对象及其派生类的对象将调用的是自己定义的negotiate方法而不是Mid类的方法。也就是说Low类的设计者不想让Low类的对象调用继承而来的方法。
● Mid类的makeLastingPeace方法的作者在其实现中发送了negotiate消息给super,故意越过了Mid类中的negotiate方法,以便执行High类中的negotiate方法。也就是第二种makeLastingPeace的实现的作者想要执行的是High类中的negotiate方法而不是别的类中的negotiate方法。
那么什么时候才能调用Mid类中的negotiate方法呢?那就是给Mid类的对象发送negotiate消息。
使用super
通过给super发送消息可以使得方法的实现分散在多个类中。在派生类对超类方法的重写中,可以通过向super发送消息来使得新的方法的实现能和原来的实现结合起来工作:
- negotiate
{
...
return [super negotiate];
}
在某些程序中,继承关系中的每一个类都可以实现某个方法的部分功能,然后通过向super发送消息的方式来让其他的类完成剩余的工作。典型的一个例子就是用来对新分配的对象进行初始化的init方法方法就是这样工作的。每一个类中的init方法都只负责对该类的实例变量进行初始化。但是在此之前,需要发送init消息给super以完成对其超类中实例变量的初始化。每一个类的init方法都是以这种固定的方式工作的,所以类中的初始化工作是按照继承关系来进行的: -(id)init
{
self = [super init];
if ( self )
{
...
}
}
初始化方法还有一些别的限制。更多关于这些限制的详细信息请参阅"分配和初始化对象"。还有一种可能使用super的地方:在超类的方法中实现核心功能,而在派生类的该方法中通过发送消息给super来使得超类中已经实现的核心功能成功派生类方法功能的一部分。例如,每个类中用于创建新对象的类方法都必须为新的对象分配存储空间并使用该类的结构对isa进行初始化。其中,分配存储空间通常是由定义在NSObject类中的alloc和allocWithZone:方法来完成的。如果别的类需要重写这两个方法(这种情况很少发生),通过向super发送消息就可以获得这个NSObject类提供的基本功能的。
深入理解self
super只是一个用来告诉编译器从什么地方开始搜寻需要执行的方法的标识。他只能作为消息的接收者来使用。但是self就不同了。self是一个变量的名字,可以有很多种使用方式。甚至可以被赋予新的值。
在类方法中使用self的情况很多。类方法通常不是和类对象有关,而是和类的实例有关。例如,很多类的实例方法中既要为实例分配空间,还要完成对该实例的初始化,同时还要设置实例变量的值。在这种情况下,把新创建的对象叫做self,正如在实例方法中的那样,并向其发送消息貌似是一种很诱人的做法。但是这样做是错误的!self和super都可以作为消息的接收者。但是在实例方法中self指的是该实例,而在类方法中self指的是该类对象。下面的代码就是错误的:
+ (Rectangle *)rectangleOfColor:(NSColor*)color
{
self = [[Rectangle alloc] init]; // 不好的写法
[self setColor:color];
return [self autorelease];
}
为了避免引起混淆,在类方法中,好的做法应该是使用别的变量名称,而不是用self来标识新生成的对象: +(id)rectangleOfColor:(NSColor *)color
{
id newInstance = [[Rectangle alloc] init];
[newInstance setColor:color];
return [newInstance];
}
实际上,在类方法中向self发送alloc消息比向类发送alloc消息更好。此时,如果该类是一个派生类,同时rectangleOfColor:消息的接收者也是一个派生类,那么方法返回的将是一个该派生类的实例。(例如,NSArray类中的array方法就可以被NSNumtableArray继承。) +(id)rectangleOfColor:(NSColor *)color
{
id newInstance = [[self alloc] init]; //很好的写法
[newInstance setColor:color];
return [newInstance autorelease];
}
译者注:实际上这部分为原文的直接翻译,使用self和具体的类名的差别从文字上没有能够明确体现出来。看看下面程序的运行结果:
// SuperClass.h
#import <Foundation/Foundation.h>
@interface SuperClass : NSObject
{
@protected
int aIntValue;
}
@property int IntValue;
+(id) createNewObj;
@end
// SuperClass.m
#import "SuperClass.h"
@implementation SuperClass
@synthesize IntValue = aIntValue;
- (id)init
{
self = [super init];
if (self)
{
// Initialization code here.
aIntValue = 0;
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
+(id) createNewObj
{
//id newObj = [[SuperClass alloc] init];
id newObj = [[self alloc] init];
return [newObj autorelease];
}
@end
//SubClass.h
#import <Foundation/Foundation.h>
#import "SuperClass.h"
@interface SubClass : SuperClass
{
@private
}
@end
// SubClass.m
#import "SubClass.h"
@implementation SubClass
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
@end
// main.m
#import <Foundation/Foundation.h>
#import "SuperClass.h"
#import "SubClass.h"
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
id obj = [SubClass createNewObj];
NSLog(@"Class:%@",[obj class]);
[pool drain];
return 0;
}
当createNewObj中使用的是SuperClass类名称的时候,程序输出如下:Class:SuperClass
当createNewObj中使用的是self关键字的时候,程序输出如下:
Class:SubClass
可见使用self的实现方法和使用固定类名称的方法相比在继承关系上来看更机动和灵活一些。