Académique Documents
Professionnel Documents
Culture Documents
Inheritance &
Polymorphism
September 17, 2009
September 17, 2009
September 17, 2009
-(void) initVar;
@end
@implementation ClassA
-(void) initVar
{
x = 100;
}
@end
September 17, 2009
@implementation ClassB
-(void) printVar
{
NSLog (@"x = %i", x);
}
@end
September 17, 2009
[b release];
[pool drain];
return 0;
}
September 17, 2009
Output?
X=100
September 17, 2009
When you send a message to an object, you might wonder how the correct method is chosen to apply to that
object. The rules are actually quite simple. First, the class to which the object belongs is checked to see
whether a method is explicitly defined in that class with the specific name. If it is, that's the method that is
used. If it's not defined there, the parent class is checked. If the method is defined there, that's what is used. If
not, the search continues.
Parent classes are checked until one of two things happens: Either you find a class that contains the specified
method or you don't find the method after going all the way back to the root class. If the first occurs, you're
all set; if the second occurs, you have a problem, and a warning message is generated that looks like this:
In this case, you inadvertently are trying to send a message called inity to a variable of type class ClassB. The
compiler told you that variables of that type of class do not know how to respond to such a method. Again,
this was determined after checking ClassB's methods and its parents' methods back to the root class (which,
in this case, is NSObject).
September 17, 2009
@end
September 17, 2009
#import "Rectangle.h"
@implementation Rectangle
-(int) perimeter
{
return (width + height) * 2;
}
@end
September 17, 2009
#import "Rectangle.h"
Output?
Rectangle: w = 5, h = 8
Area = 40, Perimeter = 26
September 17, 2009
#import "Rectangle.h"
#import "Square.h"
-(int) side
{
return width;
}
@end
September 17, 2009
#import "Square.h"
#import <Foundation/Foundation.h>
[pool drain];
return 0;
}
September 17, 2009
Output?
Square s = 5
Area = 25, Perimeter = 20
September 17, 2009
#import <Foundation/Foundation.h>
#import <Foundation/Foundation.h>
@class XYPoint;
@interface Rectangle: NSObject
{
int width;
int height;
XYPoint *origin;
}
-(XYPoint *) origin;
-(void) setOrigin: (XYPoint *) pt;
-(void) setWidth: (int) w andHeight: (int) h
-(int) area;
-(int) perimeter;
@end
September 17, 2009
You needed this because the compiler needs to know what an XYPoint
is when it encounters it as one of the instance variables defined for a
Rectangle. The class name is also used in the argument and return
type declarations for your setOrigin: and origin methods,
respectively. You do have another choice. You can import the header file
instead, like so:
#import "XYPoint.h"
#import <Foundation/Foundation.h>
#import "XYPoint.h"
@implementation XYPoint
@synthesize x, y;
-(void) setX: (int) xVal andY: (int) yVal
{
x = xVal;
y = yVal;
}
@end
September 17, 2009
#import <Foundation/Foundation.h>
@class XYPoint;
@interface Rectangle: NSObject
{
int width;
int height;
XYPoint *origin;
}
-(XYPoint *) origin;
-(void) setOrigin: (XYPoint *) pt;
-(void) setWidth: (int) w andHeight: (int) h;
-(int) area;
-(int) perimeter;
@end
September 17, 2009
#import "Rectangle.h"
@implementation Rectangle
–(int) area
{
return width * height;
}
–(int) perimeter
{
return (width + height) * 2;
}
–(XYPoint *) origin
{
return origin;
}
@end
September 17, 2009
#import "Rectangle.h"
#import "XYPoint.h"
[pool drain];
return 0;
}
September 17, 2009
OUTPUT?
Rectangle w = 5, h = 8
Origin at (100, 200)
Area = 40, Perimeter = 26
September 17, 2009
#import "Rectangle.h"
#import "XYPoint.h"
[pool drain];
return 0;
}
September 17, 2009
Why?
September 17, 2009
myRect.origin = myPoint;
the value of myPoint is passed as the argument to the method. This value points to where this
XYPoint object is stored in memory,
September 17, 2009
That value stored inside myPoint, which is a pointer into memory, is copied into the local variable pt as defined
inside the method. Now both pt and myPoint reference the same data stored in memory.
September 17, 2009
When the origin variable is set to pt inside the method, the pointer stored inside pt is copied
into the instance variable origin
September 17, 2009
Because myPoint and the origin variable stored in myRect reference the same area in memory (as does the
local variable pt), when you subsequently change the value of myPoint to (50, 50), the rectangle's origin is
changed as well.
You can avoid this problem by modifying the setOrigin: method so that it allocates its own point and sets the
origin to that point. This is shown here:
The method first allocates and initializes a new XYPoint. The message expression
sets the newly allocated XYPoint to the x, y coordinate of the argument to the method.
September 17, 2009
September 17, 2009
Be sure to now
replace:
@class directive
with
#import "XYPoint.h"
Note:
we didn't synthesize the origin methods here because the synthesized setter setOrigin:
method would have behaved just like the one you originally wrote. That is, by default, the
action of a synthesized setter is to simply copy the object pointer, not the object itself.
September 17, 2009
Method Overriding
@interface ClassA: NSObject
{
int x;
}
-(void) initVar;
@end
@implementation ClassA
-(void) initVar
{
x = 100;
}
@end
September 17, 2009
@implementation ClassB
-(void) initVar // added method
{
x = 200;
}
-(void) printVar
{
NSLog (@"x = %i", x);
}
@end
September 17, 2009
[pool drain];
return 0;
}
Output
x=200
September 17, 2009
September 17, 2009
-(void) initVar;
-(void) printVar;
@end
@implementation ClassA
-(void) initVar
{
x = 100;
}
-(void) printVar
{
NSLog (@"x = %i", x);
}
@end
September 17, 2009
#import <Foundation/Foundation.h>
-(void) initVar;
@end
@implementation ClassA
-(void) initVar
{
x = 100;
}
@end
September 17, 2009
@implementation ClassB
-(void) initVar
{
x = 200;
y = 300;
}
-(void) printVar
{
NSLog (@"x = %i", x);
NSLog (@"y = %i", y);
}
@end
September 17, 2009
[b release];
[pool drain];
return 0;
}
Output -
x = 200
y = 300
September 17, 2009
Abstract Classes
Sometimes classes are created just to make it easier for someone to create
a subclass. For that reason, these classes are called abstract classes or,
equivalently, abstract superclasses. Methods and instance variables are
defined in the class, but no one is expected to actually create an instance
from that class. For example, consider the root object NSObject.
September 17, 2009
September 17, 2009
#import <Foundation/Foundation.h>
#import "Complex.h"
@implementation Complex
-(void) print
{
NSLog (@" %g + %gi ", real, imaginary);
}
#import "Fraction.h"
#import "Complex.h"
int main (int argc, char *argv[]) // add and print 2 complex numbers
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; [c1 print]; NSLog (@" +"); [c2 print];
NSLog (@"---------");
compResult = [c1 add: c2];
Fraction *f1 = [[Fraction alloc] init];
[compResult print];
Fraction *f2 = [[Fraction alloc] init];
NSLog (@"\n");
Fraction *fracResult;
Complex *c1 = [[Complex alloc] init];
[c1 release];
Complex *c2 = [[Complex alloc] init];
[c2 release];
Complex *compResult;
[compResult release];
[pool drain];
return 0;
}
September 17, 2009
Output -
18 + 2.5i
+
-5 + 3.2i
---------
13 + 5.7i
1/10
+
2/15
----
7/30
September 17, 2009
Dynamic Binding
#import "Fraction.h"
#import "Complex.h"
id dataValue;
Fraction *f1 = [[Fraction alloc] init];
Complex *c1 = [[Complex alloc] init];
dataValue = f1;
[dataValue print];
Output -
// now dataValue gets a complex number
dataValue = c1;
[dataValue print];
2/5
[c1 release];
10 + 2.5i
[f1 release];
[pool drain];
return 0;
}
September 17, 2009
VS
id dataValue = [[Fraction alloc] init];
...
[dataValue setReal: 10.0 andImaginary: 2.5];
September 17, 2009
If an id data type can be used to store any object, why don't you just declare all your objects as type id? For several reasons, you
don't want to get into the habit of overusing this generic class data type.
First, when you define a variable to be an object from a particular class, you are using what's known as static typing. The word
static refers to the fact that the variable is always used to store objects from the particular class. So the class of the object stored in
that type is predeterminate, or static. When you use static typing, the compiler ensures, to the best of its ability, that the variable is
used consistently throughout the program. The compiler can check to ensure that a method applied to an object is defined or
inherited by that class; if not, it issues a warning message. Thus, when you declare a Rectangle variable called myRect in your
program, the compiler checks that any methods you invoke on myRect are defined in the Rectangle class or are inherited from its
superclass.
However, if the check is performed for you at runtime anyway, why do you care about static typing? You care
because it's better to get your errors out during the compilation phase of your program than during the execution
phase. If you leave it until runtime, you might not even be the one running the program when the error occurs. If your
program is put into production, some poor unsuspecting user might discover when running the program that a
particular object does not recognize a method.
September 17, 2009
September 17, 2009
if ( [mySquare isMemberOfClass: [Square class]] == YES ) if ( [Square respondsToSelector: @selector (alloc)] == YES )
NSLog (@"mySquare is a member of Square class"); NSLog (@"Square class responds to alloc method");
if ( [mySquare isKindOfClass: [Square class]] == YES ) if ([Square isSubclassOfClass: [Rectangle class]] == YES)
NSLog (@"mySquare is a kind of Square"); NSLog (@"Square is a subclass of a rectangle");
Output
#import "Fraction.h"
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Fraction *f = [[Fraction alloc] init];
[f noSuchMethod];
NSLog (@"Execution continues!");
[f release];
[pool drain];
return 0;
}
September 17, 2009
#import "Fraction.h"
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Fraction *f = [[Fraction alloc] init];
@try {
[f noSuchMethod];
}
@catch (NSException *exception) {
NSLog(@"Caught %@%@", [exception name], [exception reason]);
}
NSLog (@"Execution continues!");
[f release];
[pool drain];
return 0;
}
September 17, 2009
When the exception occurs, the @catch block gets executed. An NSException object that
contains information about the exception gets passed as the argument into this block. As you
can see, the name method retrieves the name of the exception, and the reason method gives
the reason (which the runtime system also previously printed automatically).
After the last statement in the @catch block is executed (we have only one here), the program
continues execution with the statement immediately following the block. In this case, we
execute an NSLog call to verify that execution has continued and has not been terminated.
This is a very simple example to illustrate how to catch exceptions in a program. An @finally
block can be used to include code to execute whether or not a statement in a @try block throws
an exception.
An @throw directive enables you to throw your own exception. You can use it to throw a
specific exception, or inside a @catch block to throw the same exception that took you into the
block like this:
@throw;
September 17, 2009
Lab Time!
September 17, 2009
Modify the
convertToNum() in the
Fraction class to use
exception handling -
test it!