Objective-C provides two terms that can be used within a method definition to refer to the object that performs the method—self and super.
Suppose, for example, that you define a reposition method that needs to change the coordinates of whatever object it acts on. It can invoke the setOrigin:: method to make the change. All it needs to do is send a setOrigin:: message to the same object that the reposition message itself was sent to. When you’re writing the reposition code, you can refer to that object as either self or super. The reposition method could read either:
- reposition |
{ |
... |
[self setOrigin:someX :someY]; |
... |
} |
or:
- reposition |
{ |
... |
[super setOrigin:someX :someY]; |
... |
} |
Here, self and super both refer to the object receiving a reposition message, whatever object that may happen to be. The two terms are quite different, however. self is one of the hidden arguments that the messaging routine passes to every method; it’s a local variable that can be used freely within a method implementation, just as the names of instance variables can be. super is a term that substitutes for self only as the receiver in a message expression. As receivers, the two terms differ principally in how they affect the messaging process:
self searches for the method implementation in the usual manner, starting in the dispatch table of the receiving object’s class. In the example above, it would begin with the class of the object receiving the reposition message.
super starts the search for the method implementation in a very different place. It begins in the superclass of the class that defines the method where super appears. In the example above, it would begin with the superclass of the class where reposition is defined.
Wherever super receives a message, the compiler substitutes another messaging routine for the objc_msgSend function. The substitute routine looks directly to the superclass of the defining class—that is, to the superclass of the class sending the message to super—rather than to the class of the object receiving the message.
An Example
Using super
Redefining self
The difference between self and super becomes clear in a hierarchy of three classes. Suppose, for example, that we create an object belonging to a class called Low. Low’s superclass is Mid; Mid’s superclass is High. All three classes define a method called negotiate, which they use for a variety of purposes. In addition, Mid defines an ambitious method called makeLastingPeace, which also has need of the negotiate method. This is illustrated in Figure 7-2:
We now send a message to our Low object to perform the makeLastingPeace method, and makeLastingPeace, in turn, sends a negotiate message to the same Low object. If source code calls this object self,
- makeLastingPeace |
{ |
[self negotiate]; |
... |
} |
the messaging routine finds the version of negotiate defined in Low, self’s class. However, if Mid’s source code calls this object super,
- makeLastingPeace |
{ |
[super negotiate]; |
... |
} |
the messaging routine will find the version of negotiate defined in High. It ignores the receiving object’s class (Low) and skips to the superclass of Mid, since Mid is where makeLastingPeace is defined. Neither message finds Mid’s version of negotiate.
As this example illustrates, super provides a way to bypass a method that overrides another method. Here it enabled makeLastingPeace to avoid the Mid version of negotiate that redefined the original High version.
Not being able to reach Mid’s version of negotiate may seem like a flaw, but, under the circumstances, it’s right to avoid it:
The author of the Low class intentionally overrode Mid’s version of negotiate so that instances of the Low class (and its subclasses) would invoke the redefined version of the method instead. The designer of Low didn’t want Low objects to perform the inherited method.
In sending the message to super, the author of Mid’s makeLastingPeace method intentionally skipped over Mid’s version of negotiate (and over any versions that might be defined in classes like Low that inherit from Mid) to perform the version defined in the High class. Mid’s designer wanted to use the High version of negotiate and no other.
Mid’s version of negotiate could still be used, but it would take a direct message to a Mid instance to do it.
Messages to super allow method implementations to be distributed over more than one class. You can override an existing method to modify or add to it, and still incorporate the original method in the modification:
- negotiate |
{ |
... |
return [super negotiate]; |
} |
For some tasks, each class in the inheritance hierarchy can implement a method that does part of the job and passes the message on to super for the rest. The init method, which initializes a newly allocated instance, is designed to work like this. Each init method has responsibility for initializing the instance variables defined in its class. But before doing so, it sends an init message to super to have the classes it inherits from initialize their instance variables. Each version of init follows this procedure, so classes initialize their instance variables in the order of inheritance:
- (id)init |
{ |
[super init]; |
... |
} |
It’s also possible to concentrate core functionality in one method defined in a superclass, and have subclasses incorporate the method through messages to super. For example, every class method that creates an instance must allocate storage for the new object and initialize its isa pointer to the class structure. This is typically left to the alloc and allocWithZone: methods defined in the NSObject class. If another class overrides these methods (a rare case), it can still get the basic functionality by sending a message to super.
super is simply a flag to the compiler telling it where to begin searching for the method to perform; it’s used only as the receiver of a message. But self is a variable name that can be used in any number of ways, even assigned a new value.
There’s a tendency to do just that in definitions of class methods. Class methods are often concerned not with the class object, but with instances of the class. For example, many class methods combine allocation and initialization of an instance, often setting up instance variable values at the same time. In such a method, it might be tempting to send messages to the newly allocated instance and to call the instance self, just as in an instance method. But that would be an error. self and super both refer to the receiving object—the object that gets a message telling it to perform the method. Inside an instance method, self refers to the instance; but inside a class method, self refers to the class object. This is an example of what not to do:
+ (Rectangle *)rectangleOfColor:(NSColor *) color |
{ |
self = [[Rectangle alloc] init]; // BAD |
[self setColor:color]; |
return [self autorelease]; |
} |
To avoid confusion, it’s usually better to use a variable other than self to refer to an instance inside a class method:
+ (id)rectangleOfColor:(NSColor *)color |
{ |
id newInstance = [[Rectangle alloc] init]; // GOOD |
[newInstance setColor:color]; |
return [newInstance autorelease]; |
} |
In fact, rather than sending the alloc message to the class in a class method, it’s often better to send alloc to self. This way, if the class is subclassed, and the rectangleOfColor: message is received by a subclass, the instance returned will be the same type as the subclass (for example, the array method of NSArray is inherited by NSMutableArray).
+ (id)rectangleOfColor:(NSColor *)color |
{ |
id newInstance = [[self alloc] init]; // EXCELLENT |
[newInstance setColor:color]; |
return [newInstance autorelease]; |
} |
See “Allocating and Initializing Objects” for more information about object allocation.
Last updated: 2008-02-05