Objects in Objective-C are defined in terms of a class. New classes of objects are specializations of a more general class. Each new class is the accumulation of the class definitions that it inherits from and can expand on that definition by adding new methods and instance variables or redefining existing methods to perform new or expanded functionality. Like Java and Smalltalk, but unlike C++, Objective-C is a single inheritance language, meaning that a class can inherit functionality only from a single class.
A class is not just a blueprint for building objects; it is itself an object in the runtime that knows how to build new objects. These new objects are instances of the class.
Every class hierarchy begins with a root class that has no superclass. While it is possible to define your own root class in Objective-C, the classes you define should inherit, directly or indirectly, from the NSObject class provided by the Foundation framework. The NSObject class defines the behavior required for an object to be used by the Cocoa framework and provides the following functionality:
Defines the low-level functionality needed to handle object initialization, duplication, and destruction.
Provides mechanisms to aid Cocoa's memory management model.
Defines functionality for an object to identify its class membership and provide a reasonable description of the object.
In Objective-C, classes are defined in two parts, usually separated into two different files:
An interface, which declares a class's methods and instance variables, and names its superclass. The interface is usually specified in a file with the .h suffix typical of C header files.
An implementation, which contains the code that defines the class's methods. By convention, files containing the implementation of a class have a .m suffix.
To declare a class and give all the information other classes (and other programs) need to use it, an interface file needs to contain the following information:
The class that is being inherited from
The instance variables, if any, that the class adds
A list of method declarations, if any, indicating what methods the class adds or modifies significantly
Example 1-1 shows simple header file, saved by convention as Song.h, containing the interface for the Song class.
#import <Cocoa/Cocoa.h> // 1 @interface Song : NSObject { // 2 id title; // 3 } - (id)title; // 4 - (void)setTitle:(id)aTitle; // 5 @end; // 6
Each line is defined as follows:
Imports the definitions for the Cocoa frameworks. This line is similar to the #include directive in C, except the compiler ensures that it doesn't include a header file more than once.
Declares the name of the class, Song, and specifies NSObject as its superclass.
Declares an instance variable named title. The id type indicates that the variable is an object. If we wanted the compiler to enforce type checking for us, we could declare its type as NSString *.
Declares an instance method named title that returns an object. The - (minus sign) before the method name indicates that the method is an instance method.
Declares an instance method named setTitle that takes an object argument and doesn't return anything.
The @end; statement indicates to the compiler the end of the Song class interface.
The object-oriented principle of encapsulation means that other programmers shouldn't need to know a class's instance variables. Instead, they need to know only the messages that can be sent to a class. The inclusion of instance variables in the interface file, while required by C, would seem to break encapsulation.
To give a class the ability to enforce encapsulation even though the variables are declared in the header file, the compiler limits the scope of the class's instance variables to the class that declares them and its subclasses. This enforcement can be changed by using the following set of compiler directives:
These instances are accessible within the class from which they are declared. Subclasses will not be able to access them.
These instances are available within the class that declares them and within classes that inherit from them. This is a variable's default scope.
These instances are available to any class and can be used by code as if they were a field in a C structure. However, the directive should not be used except when absolutely necessary, because it defeats the purpose of encapsulation.
For example, to ensure that subclasses of the Song class could not directly access the title instance variable, use the @private directive as shown in Example 1-2.
#import <Cocoa/Cocoa.h> @interface Song : NSObject { @private id title; } - (id)title; - (void)setTitle:(id)aTitle; @end;
To define how the class works, an implementation file needs to contain implementations of the methods defined in the interface file. Example 1-3 shows the implementation, contained in the source file Song.m by convention, of the Song class.
#import Song.h // 1 @implementation Song // 2 - (id)title { // 3 return title; } - (void)setTitle:(id)aTitle { // 4 [title autorelease]; title = [aTitle retain]; } @end // 5
Here is a detailed explanation of each part of this code:
Imports the header file that contains the interface for the file. Every implementation must import its own interface.
Declares that what follows is the implementation of the Song class.
Implementation of the title method. This method simply returns the title variable's value. The contents of a method are defined, like C functions, between a pair of braces. Also, the class's instance variables are in the scope of the method and can be referred to directly.
Implementation of the setTitle method. This method sets the title variable to the aTitle argument after performing some steps, using the retain and autorelease messages required for proper memory management. For more information about memory management, see Section 1.5, later in this chapter.
Indicates to the compiler the end of the Song class implementation.
Notice that the implementation doesn't need to repeat the superclass name or the instance variable declarations.
In addition to a class's instance variables, several other instance variables are defined within the scope of instance methods. These variables are:
Defined by the NSObject class, the isa variable contains a pointer to the class object. This lets an object introspect itself. It is also what lets the runtime determine what kind of object it is when it resolves messages to methods.
A variable set by the runtime to point at the object the action is performed onthe receiver object of the message. This allows the functionality within a method to send messages to the object on which the method acts.
A variable set by the runtime that behaves similarly to self, except that the resolution of message to method starts with the object's superclass. This allows you to call the functionality of superclasses.
The selector used to call the current method.
Since classes are objects, you can define methods that will act when messages are sent to a class. Class methods are defined in the same way as instance methods, except you use a plus symbol (+) at the beginning of the method declaration instead of a hyphen or minus sign (-). For example, if the Song class keeps track of the number of songs created, a numberOfSongs class method could be provided, as shown in Example 1-4.
#import <Cocoa/Cocoa.h> @interface Song : NSObject { id title; } + (int)numberOfSongs; - (id)title; - (void)setTitle:(id)aTitle; @end;
Similarly, this method's implementation is placed between the @implementation and @end directives in the implementation (.m) file. Since a class method operates on the class object, the isa, self, super, and _cmd variables are defined the same way as instance variables.
|
When a new class is defined, a method can be implemented with the same name as a method in one of the superclasses up the inheritance hierarchy. This new method overrides the original when messages with the method name are sent to the derived class's object. When overriding methods, you can access the superclass's method functionality by sending a message to the special variable super.
For example, if the class of iPod inherits from a more generic MP3Player class that also defines the play method, the subclass's play method may require that the superclass functionality is executed. Example 1-5 shows how this could be achieved by using the super variable.
- (void)play { [self setPlayIndicator:YES]; [super play]; }
When a superclass method is overridden, the method doesn't need to be declared again in the interface (.h) file. By convention, an overridden method is listed in the interface file only if you significantly change the way the method works.
|