8/14/2019 Objective C - Week 5 - Thursday
1/63
September 17, 20
Inheritance &
Polymorphism
8/14/2019 Objective C - Week 5 - Thursday
2/63
September 17, 20
8/14/2019 Objective C - Week 5 - Thursday
3/63
September 17, 20
8/14/2019 Objective C - Week 5 - Thursday
4/63
September 17, 20
@interface ClassA: NSObject
{
int x;}
-(void) initVar;
@end
@implementation ClassA
-(void) initVar
{ x = 100;}
@end
8/14/2019 Objective C - Week 5 - Thursday
5/63
September 17, 20
@interface ClassB : ClassA
-(void) printVar;
@end
@implementation ClassB
-(void) printVar
{ NSLog (@"x = %i", x);}
@end
8/14/2019 Objective C - Week 5 - Thursday
6/63
September 17, 20
int main (int argc, char *argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
ClassB *b = [[ClassB alloc] init];
[b initVar]; // will use inherited method
[b printVar]; // reveal value of x;
[b release];
[pool drain];
return 0;
}
8/14/2019 Objective C - Week 5 - Thursday
7/63
September 17, 20
Output?
X=100
8/14/2019 Objective C - Week 5 - Thursday
8/63
September 17, 20
Finding the Right Method
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:
warning: 'ClassB' may not respond to '-inity'
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).
8/14/2019 Objective C - Week 5 - Thursday
9/63
September 17, 20
@interface Rectangle: NSObject{ intwidth; int height;}
@property int width, height;
-(int) area;-(int) perimeter;-(void) setWidth: (int) w andHeight: (int) h;
@end
8/14/2019 Objective C - Week 5 - Thursday
10/63
September 17, 20
#import "Rectangle.h"
@implementation Rectangle@synthesize width, height;-(void) setWidth: (int) w andHeight: (int) h{ width = w; height = h;}-(int) area{ return width * height;}-(int) perimeter{ return (width + height) * 2;}@end
8/14/2019 Objective C - Week 5 - Thursday
11/63
September 17, 20
#import "Rectangle.h"
int main (int argc, char *argv[])
{ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Rectangle *myRect = [[Rectangle alloc] init]; [myRect setWidth: 5 andHeight: 8]; NSLog (@"Rectangle: w = %i, h = %i", myRect.width, myRect.height); NSLog (@"Area = %i, Perimeter = %i", [myRect area], [myRect perimeter]); [myRect release]; [pool drain]; return 0;}
8/14/2019 Objective C - Week 5 - Thursday
12/63
September 17, 20
Output?
Rectangle: w = 5, h = 8
Area = 40, Perimeter = 26
8/14/2019 Objective C - Week 5 - Thursday
13/63
September 17, 20
#import "Rectangle.h"
@interface Square: Rectangle
-(void) setSide: (int) s;
-(int) side;
@end
8/14/2019 Objective C - Week 5 - Thursday
14/63
September 17, 20
#import "Square.h"
@implementation Square: Rectangle
-(void) setSide: (int) s
{ [self setWidth: s andHeight: s];}
-(int) side
{return width;}
@end
8/14/2019 Objective C - Week 5 - Thursday
15/63
September 17, 20
#import "Square.h"
#import
int main (int argc, char *argv[])
{ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Square *mySquare = [[Square alloc] init]; [mySquare setSide: 5]; NSLog (@"Square s = %i", [mySquare side]); NSLog (@"Area = %i, Perimeter = %i", [mySquare area], [mySquare perimeter]); [mySquare release]; [pool drain]; return 0;}
8/14/2019 Objective C - Week 5 - Thursday
16/63
September 17, 20
Output?
Square s = 5
Area = 25, Perimeter = 20
8/14/2019 Objective C - Week 5 - Thursday
17/63
September 17, 20
#import
@interface XYPoint: NSObject
{ int x; int y;}
@property int x, y;
-(void) setX: (int) xVal andY: (int) yVal;
@end
8/14/2019 Objective C - Week 5 - Thursday
18/63
September 17, 20
#import
@class XYPoint;
@interface Rectangle: NSObject{ int width; int height; XYPoint *origin;}
@property int width, height;
-(XYPoint *) origin;
-(void)setOrigin: (XYPoint *) pt;
-(void) setWidth: (int) w andHeight: (int) h-(int) area;-(int) perimeter;@end
8/14/2019 Objective C - Week 5 - Thursday
19/63
September 17, 20
You used a new directive in the Rectangle.h header file:
@class XYPoint;
You needed this because the compiler needs to know what an XYPointis 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 yoursetOrigin: and origin methods,respectively. You do have another choice. You can import the header fileinstead, like so:
#import "XYPoint.h"
Using the @class directive is more efficient because the compiler
doesn't need to process the entire XYPoint.h file (even though it is
quite small); it just needs to know that XYPoint is the name of a class.
If you need to reference one of the XYPoint classes methods, the
@class directive does not suffice because the compiler would needmore information; it would need to know how many arguments themethod takes, what their types are, and what the method's return typeis.
8/14/2019 Objective C - Week 5 - Thursday
20/63
September 17, 20
#import
@interface XYPoint: NSObject
{ int x; int y;}
@property int x, y;
-(void) setX: (int) xVal andY: (int) yVal;
@end
8/14/2019 Objective C - Week 5 - Thursday
21/63
September 17, 20
#import "XYPoint.h"
@implementation XYPoint
@synthesize x, y;
-(void) setX: (int) xVal andY: (int) yVal
{ x = xVal; y = yVal;}
@end
8/14/2019 Objective C - Week 5 - Thursday
22/63
September 17, 20
#import
@class XYPoint;
@interface Rectangle: NSObject
{ int width; int height; XYPoint *origin;}
@property int width, height;
-(XYPoint *) origin;
-(void) setOrigin: (XYPoint *) pt;-(void) setWidth: (int) w andHeight: (int) h;-(int) area;-(int) perimeter;@end
8/14/2019 Objective C - Week 5 - Thursday
23/63
September 17, 20
#import "Rectangle.h"
@implementation Rectangle
@synthesize width, height;
-(void) setWidth: (int) w andHeight: (int) h
{
width = w; height = h;}
(void) setOrigin: (XYPoint *) pt
{ origin = pt;}
(int) area
{ return width * height;}
(int) perimeter
{ return (width + height) * 2;}
(XYPoint *) origin
{ return origin;}
@end
8/14/2019 Objective C - Week 5 - Thursday
24/63
September 17, 20
#import "Rectangle.h"
#import "XYPoint.h"
int main (int argc, char *argv[])
{ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Rectangle *myRect = [[Rectangle alloc] init]; XYPoint *myPoint = [[XYPoint alloc] init]; [myPoint setX: 100 andY: 200]; [myRect setWidth: 5 andHeight: 8]; myRect.origin = myPoint; NSLog (@"Rectangle w = %i, h = %i", myRect.width, myRect.height); NSLog (@"Origin at (%i, %i)", myRect.origin.x, myRect.origin.y); NSLog (@"Area = %i, Perimeter = %i", [myRect area], [myRect perimeter]); [myRect release]; [myPoint release]; [pool drain]; return 0;}
8/14/2019 Objective C - Week 5 - Thursday
25/63
September 17, 20
OUTPUT?
Rectangle w = 5, h = 8
Origin at (100, 200)
Area = 40, Perimeter = 26
8/14/2019 Objective C - Week 5 - Thursday
26/63
September 17, 20
#import "Rectangle.h"
#import "XYPoint.h"
int main (int argc, char *argv[])
{ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Rectangle *myRect = [[Rectangle alloc] init]; XYPoint *myPoint = [[XYPoint alloc] init]; [myPoint setX: 100 andY: 200]; [myRect setWidth: 5 andHeight: 8]; myRect.origin = myPoint; NSLog (@"Origin at (%i, %i)", myRect.origin.x, myRect.origin.y); [myPoint setX: 50 andY: 50]; NSLog (@"Origin at (%i, %i)", myRect.origin.x, myRect.origin.y); [myRect release]; [myPoint release]; [pool drain]; return 0;}
8/14/2019 Objective C - Week 5 - Thursday
27/63
September 17, 20
Now what's theoutput?
8/14/2019 Objective C - Week 5 - Thursday
28/63
September 17, 20
Origin at (100, 200)
Origin at (50, 50)
Why?
8/14/2019 Objective C - Week 5 - Thursday
29/63
September 17, 20
When the setOrigin: method is invoked with the expression
myRect.origin = myPoint;
the value of myPoint is passed as the argument to the method. This value points to where thisXYPoint object is stored in memory,
8/14/2019 Objective C - Week 5 - Thursday
30/63
September 17, 20
That value stored inside myPoint, which is a pointer into memory, is copied into the local variable pt as definedinside the method. Now both pt and myPoint reference the same data stored in memory.
8/14/2019 Objective C - Week 5 - Thursday
31/63
September 17, 20
When the origin variable is set to pt inside the method, the pointer stored inside pt is copiedinto the instance variable origin
8/14/2019 Objective C - Week 5 - Thursday
32/63
September 17, 20
Because myPoint and the origin variable stored in myRect reference the same area in memory (as does thelocal variable pt), when you subsequently change the value of myPoint to (50, 50), the rectangle's origin ischanged as well.
You can avoid this problem by modifying the setOrigin: method so that it allocates its own point and sets theorigin to that point. This is shown here:
-(void) setOrigin: (XYPoint *) pt{
origin = [[XYPoint alloc] init];
[origin setX: pt.x andY: pt.y];}
The method first allocates and initializes a new XYPoint. The message expression
[origin setX: pt.x andY: pt.y];
sets the newly allocated XYPoint to the x, y coordinate of the argument to the method.
8/14/2019 Objective C - Week 5 - Thursday
33/63
September 17, 20
8/14/2019 Objective C - Week 5 - Thursday
34/63
September 17, 20
#import "XYPoint.h"
Be sure to nowreplace:
@class directivewith
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, theaction of a synthesized setter is to simply copy the object pointer, not the object itself.
8/14/2019 Objective C - Week 5 - Thursday
35/63
September 17, 20
Method Overriding
@interface ClassA: NSObject
{ int x;}
-(void) initVar;
@end
@implementation ClassA
-(void) initVar
{ x = 100;}
@end
8/14/2019 Objective C - Week 5 - Thursday
36/63
September 17, 20
@interface ClassB: ClassA
-(void) initVar;
-(void) printVar;
@end
@implementation ClassB
-(void) initVar // added method{ x = 200;}
-(void) printVar
{ NSLog (@"x = %i", x);}@end
8/14/2019 Objective C - Week 5 - Thursday
37/63
September 17, 20
int main (int argc, char *argv[])
{ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; ClassB *b = [[ClassB alloc] init]; [b initVar]; // uses overriding method in B [b printVar]; // reveal value of x; [b release]; [pool drain]; return 0;}
Outputx=200
8/14/2019 Objective C - Week 5 - Thursday
38/63
September 17, 20
8/14/2019 Objective C - Week 5 - Thursday
39/63
September 17, 20
@interface ClassA: NSObject
{
int x;
}
-(void) initVar;
-(void) printVar;
@end
@implementation ClassA
-(void) initVar
{
x = 100;
}
-(void) printVar
{NSLog (@"x = %i", x);
}
@end
Now let's add a printvar() to ClassA
8/14/2019 Objective C - Week 5 - Thursday
40/63
September 17, 20
#import
// insert definitions for ClassA and ClassB here
int main (int argc, char *argv[])
{NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
ClassA *a = [[ClassA alloc] init];
ClassB *b = [[ClassB alloc] init];
[a initVar]; // uses ClassA method
[a printVar]; // reveal value of x;
[b initVar]; // use overriding ClassB method
[b printVar]; // reveal value of x;
[a release];
[b release];
[pool drain];
return 0;
}
Output -x = 100x = 200
8/14/2019 Objective C - Week 5 - Thursday
41/63
September 17, 20
Adding Instance Variables
@interface ClassA: NSObject
{
int x;
}
-(void) initVar;
@end
@implementation ClassA
-(void) initVar
{
x = 100;
}
@end
8/14/2019 Objective C - Week 5 - Thursday
42/63
September 17, 20
@interface ClassB: ClassA
{ int y;}
-(void) initVar;-(void)
printVar;@end
@implementation ClassB
-(void) initVar
{ x = 200; y = 300;}
-(void) printVar
{
NSLog (@"x = %i", x); NSLog (@"y = %i", y);}
@end
8/14/2019 Objective C - Week 5 - Thursday
43/63
September 17, 20
int main (int argc, char *argv[])
{ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; ClassB *b = [[ClassB alloc] init]; [b initVar]; // uses overriding method in ClassB [b printVar]; // reveal values of x and y; [b release]; [pool drain]; return 0;}
Output -
x = 200
y = 300
8/14/2019 Objective C - Week 5 - Thursday
44/63
September 17, 20
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.
8/14/2019 Objective C - Week 5 - Thursday
45/63
September 17, 20
8/14/2019 Objective C - Week 5 - Thursday
46/63
September 17, 20
Polymorphism, Dynamic Typing, and Dynamic Binding
Polymorphism enables programs to be developed so that
objects from different classes can define methods that share the
same name.Dynamic typingdefers the determination of the
class that an object belongs to until the program is executing.
Dynamic bindingdefers the determination of the actual method
to invoke on an object until program execution time.
8/14/2019 Objective C - Week 5 - Thursday
47/63
September 17, 20
// Interface file for Complex class
#import
@interface Complex: NSObject
{
double real;
double imaginary;
}
@property double real, imaginary;
-(void) print;
-(void) setReal: (double) a andImaginary: (double) b;-(Complex *) add: (Complex *) f;
@end
8/14/2019 Objective C - Week 5 - Thursday
48/63
September 17, 20
#import "Complex.h"
@implementation Complex
@synthesize real, imaginary;
-(void) print
{ NSLog (@" %g + %gi ", real, imaginary);}
-(void) setReal: (double) a andImaginary: (double) b
{ real = a; imaginary = b;}
-(Complex *) add: (Complex *) f
{ Complex *result = [[Complex alloc] init]; [result setReal: real + [f real] andImaginary: imaginary + [f imaginary]]; return result;}
@end
8/14/2019 Objective C - Week 5 - Thursday
49/63
September 17, 20
#import "Fraction.h"
#import "Complex.h"
int main (int argc, char *argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Fraction *f1 = [[Fraction alloc] init];
Fraction *f2 = [[Fraction alloc] init];
Fraction *fracResult;
Complex *c1 = [[Complex alloc] init];
Complex *c2 = [[Complex alloc] init];
Complex *compResult;
[f1 setTo: 1 over: 10];
[f2 setTo: 2 over: 15];
[c1 setReal: 18.0 andImaginary: 2.5];
[c2 setReal: -5.0 andImaginary: 3.2];
// add and print 2 complex numbers
[c1 print]; NSLog (@" +"); [c2 print];
NSLog (@"---------");
compResult = [c1 add: c2];[compResult print];
NSLog (@"\n");
[c1 release];
[c2 release];
[compResult release];
// add and print 2 fractions
[f1 print]; NSLog (@" +"); [f2 print];
NSLog (@"----");
fracResult = [f1 add: f2];
[fracResult print];
[f1 release];
[f2 release];
[fracResult release];
[pool drain];
return 0;
}
8/14/2019 Objective C - Week 5 - Thursday
50/63
September 17, 20
18 + 2.5i
+
-5 + 3.2i
---------
13 + 5.7i
1/10
+
2/15
----
7/30
Output -
8/14/2019 Objective C - Week 5 - Thursday
51/63
September 17, 20
The id data type is a generic object type. Thatis, id can be used for storing objects that belongto any class. The real power of this data type isexploited when it's used this way to storedifferent types of objects in a variable during theexecution of a program.
Dynamic Binding
8/14/2019 Objective C - Week 5 - Thursday
52/63
September 17, 20
#import "Fraction.h"
#import "Complex.h"
int main (int argc, char *argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
id dataValue;
Fraction *f1 = [[Fraction alloc] init];Complex *c1 = [[Complex alloc] init];
[f1 setTo: 2 over: 5];
[c1 setReal: 10.0 andImaginary: 2.5];
// first dataValue gets a fraction
dataValue = f1;
[dataValue print];
// now dataValue gets a complex number
dataValue = c1;
[dataValue print];
[c1 release];[f1 release];
[pool drain];
return 0;
}
Output -
2/510 + 2.5i
8/14/2019 Objective C - Week 5 - Thursday
53/63
September 17, 20
Compile Time Versus Runtime Checking
Because the type of object stored inside an id variable can be
indeterminate at compile time, some tests are deferred until
runtimethat is, while the program is executing.
Fraction *f1 = [[Fraction alloc] init];
[f1 setReal: 10.0 andImaginary: 2.5];
id dataValue = [[Fraction alloc] init];...
[dataValue setReal: 10.0 andImaginary: 2.5];
VS
8/14/2019 Objective C - Week 5 - Thursday
54/63
September 17, 20
The id Data Type and Static Typing
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.
8/14/2019 Objective C - Week 5 - Thursday
55/63
September 17, 20
8/14/2019 Objective C - Week 5 - Thursday
56/63
September 17, 20
Methods for Working with Dynamic Types
Method Question or Action
-(BOOL) isKindOfClass: class-object Is the object a member of class-object or adescendant?
-(BOOL) isMemberOfClass:
class-object
Is the object a member of class-object?
-(BOOL) respondsToSelector:
selector
Can the object respond to the method specified
by selector?
+(BOOL) instancesRespondToSelector:
selector
Can instances of the specified class respond to
selector?
+(BOOL) isSubclassOfClass:
class-object
Is the object a subclass of the specified class?
-(id) performSelector: selector Apply the method specified by selector .
-(id) performSelector:
selector withObject: object
Apply the method specified by selector ,
passing the argument object.
-(id) performSelector:
selector withObject: object1
withObject: object2
Apply the method specified by selector with
the arguments object1 and object2.
8/14/2019 Objective C - Week 5 - Thursday
57/63
September 17, 20
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;
}
8/14/2019 Objective C - Week 5 - Thursday
58/63
September 17, 20
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
Output
8/14/2019 Objective C - Week 5 - Thursday
59/63
September 17, 20
Exception Handling Using @try
#import "Fraction.h"
int main (int argc, char *argv [])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Fraction *f = [[Fraction alloc] init];
[f noSuchMethod];
NSLog (@"Execution continues!");
[f release];
[pool drain];return 0;
}
8/14/2019 Objective C - Week 5 - Thursday
60/63
September 17, 20
#import "Fraction.h"
int main (int argc, char *argv [])
{
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;
}
8/14/2019 Objective C - Week 5 - Thursday
61/63
September 17, 20
When the exception occurs, the @catch block gets executed. An NSException object thatcontains 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 programcontinues 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 throwsan 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 theblock like this:
@throw;
8/14/2019 Objective C - Week 5 - Thursday
62/63
September 17, 20
Lab Time!
8/14/2019 Objective C - Week 5 - Thursday
63/63
September 17, 20
Modify theconvertToNum() in theFraction class to useexception handling -
test it!