Advertisements

Objects (Part 3): Extending the Fraction Class


Welcome back, and welcome to 2011! Let’s dive right in—today, we’ll be extending our Fraction class, and covering some miscellaneous concepts of OOP.

Implementing New Methods

The last Extension, which described methods in detail, mentioned a method called setNumerator:overDenominator:. As you might have guessed, we are going to implement this functionality into our Fraction class, and see how we use a method with multiple arguments.

Open up Fraction.h from the project we’ve been working on; alternatively, you can download the files by clicking here. Add the following line to the list of methods (it doesn’t matter exactly where you place it, as long as it’s between the @propertys and the final @end):

- (void)setNumerator:(NSInteger)num overDenominator:(NSInteger)denom;

In Fraction.m, add the following implementation for the method:

- (void)setNumerator:(NSInteger)num overDenominator:(NSInteger)denom {
	self.numerator = num;
	self.denominator = denom;
}

Note that the names of the arguments in this method are num and denom are not the same as the instance variables numerator and denominator. This is because every instance method has access to the class’s instance variables (remember that a class is just an object that gets created at runtime—hypothetically, it would be weird if a car did not have access to its current speed (speedometer) or fuel level). Because of this, if your argument names are the same name as an instance variable, and you try to reference the argument or the instance variable, the compiler will get confused. At best, you’ll have runtime issues; at worst, the program will refuse to compile.

Update the main function (FractionDemo.m) to have the following code:

#import "Fraction.h"
#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    Fraction *myFraction = [[Fraction alloc] init];
	
	// Set myFraction to value of 2/5
	/* [myFraction setNumerator: 2];
	[myFraction setDenominator: 5]; */
	
	/* myFraction.numerator = 2;
	myFraction.denominator = 5; */
	
	[myFraction setNumerator:2 overDenominator:5];
	
	// Display the value of myFraction
	NSLog(@"myFraction has a value of: ");
	[myFraction display];
	
	[myFraction release];	
    [pool drain];
    return 0;
}

Just note that the main() function is where any program starts executing. Here, we are placing our actual program (where something is done) inside the main() routine; when we begin building iPhone apps, we will almost never modify main().

Build and run your program. The output should be exactly as expected:

 myFraction has a value of: 
2/5

Fraction Operations

Here, we will implement an instance method to add two fractions. As an exercise to you, try to implement subtraction, multiplication, and division! The code will be available for download at the end of this post, but try to do it on your own.

(a/b) + (c/d) = (ad + bc)/(bd)

Adding Fractions

Our method looks like this:

- (void)add:(Fraction *)newFraction;

Note that we are passing in another Fraction object (technically, the asterisk symbolizes that we are passing in a pointer, or reference to a Fraction object). Also note that although we are only passing in one value to be added, the other (implied) value in the addition is the instance of Fraction that we’re calling this method on. Every method has access to its own instance variables, and as we’ll discover later in this post, every method can reference self.

The implementation of the method is as follows:

- (void)add:(Fraction *)newFraction {
	// a/b + c/d = ((a * d) + (b * c)) / (b * d)
	self.numerator = self.numerator * newFraction.denominator + self.denominator * newFraction.numerator;
	self.denominator = self.denominator * newFraction.denominator;
}

The parentheses were not strictly necessary, but made it easier to read and understand.

Let’s test this new method:

#import "Fraction.h"
#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    Fraction *aFraction = [[Fraction alloc] init];
	Fraction *bFraction = [[Fraction alloc] init];
	
	[aFraction setNumerator:1 overDenominator:2];
	[bFraction setNumerator:1 overDenominator:3];
	
	[aFraction display]; NSLog(@" + "); [bFraction display]; NSLog(@" = ");
	[aFraction add:bFraction];
	[aFraction display];
	
	[aFraction release];	
	[bFraction release];
    [pool drain];
    return 0;
}

The result:

1/2
+
1/3
=
5/6

This is exactly correct.

Variable Scope

If we try our program with different numbers (say, 2/4 and 1/3, which do happen to have the same values), we get a result of 10/12. Our current add method simply adds, without reducing the value. Let’s implement a method that reduces any Fraction:

- (void)reduce {
	int u = self.numerator;
	int v = self.denominator;
	int temp = 0;
	
	// Euclid's procedure to find GCD (Greatest Common Denominator)
	// Don't worry about how this works, exactly. 
	
	while (v != 0) {
		temp = u % v;
		u = v;
		v = temp;
	}
	
	self.numerator /= u;
	self.denominator /= u;
}

This method declares three integer values called u, v, and temp. These are local variables—they only exist during the execution of the reduce method, they can only be accessed from the same method, and get cleared once the method is done. In more formal parlance, these variables’ scope is limited to this method. These variables must be initialized (given a value) before they are used; every time the method is invoked (called), these values are set to the default initial values.

Incidentally, this scope also applies to variables defined within loops and if() blocks—those variables would only be accessible within the same block. As a general rule of thumb, a variable is always and only accessible in the same block as the declaration itself. A block is delineated by curly braces ({ }).

The same scope is applied to method arguments. The arguments contain a copy of the value that you pass in, not the original. For primitive types, such as ints or floats, this means that you cannot modify the original value that you passed in, only the copy. Whenever you pass in an object (more precisely, an object pointer—more on that in the next Extension), you can, in fact, modify the original object, because a copy is made of the memory address, not of the object itself. Don’t worry if that last line was confusing; it’ll be quickly cleared up.

Let’s test our reduce method:

#import "Fraction.h"
#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    Fraction *aFraction = [[Fraction alloc] init];
	Fraction *bFraction = [[Fraction alloc] init];
	
	[aFraction setNumerator:2 overDenominator:4];
	[bFraction setNumerator:1 overDenominator:3];
	
	[aFraction display]; NSLog(@" + "); [bFraction display]; NSLog(@" = ");
	[aFraction add:bFraction];
	[aFraction reduce];
	[aFraction display];
	
	[aFraction release];	
	[bFraction release];
    [pool drain];
    return 0;
}

The resulting fraction is the expected 5/6, not 10/12.

Static variables

A static variable is one that retains its value through multiple method calls to the same variable. Unlike a regular variable declared in a method, which gets cleared after the method is finished, a static variable does not get cleared. A static variable is automatically set to 0 at creation:

static int counter;

You do not need to set the variable to 0; that is already done.

Although static variables retain their value through successive method calls, they are still only accessible in that method. Some static variables are placed outside any method, sometimes even before the @implementation line, so that they can accessed in any method.

If a variable is a local static variable, it could be used anytime that method in which it was defined is called. If it were an instance variable, it would be able to be changed from any method. This makes it very similar to a static variable outside any method. The difference at this point is a matter of conventions. If you need to keep track of a value which does not necessary seem to “belong” to an object (for instance, the number of potholes a Car object has metaphorically gone over probably won’t be an instance variable of Car (because it is not a property of Cars), but could still be a static variable).

self

The self keyword is used to access the receiver of the method—the same object that sent the message in the first place. For instance, in our add: method, we could have reduced the value directly in the method:

- (void)add:(Fraction *)newFraction {
	// a/b + c/d = ((a * d) + (b * c)) / (b * d)
	self.numerator = self.numerator * newFraction.denominator + self.denominator * newFraction.numerator;
	self.denominator = self.denominator * newFraction.denominator;
	
	[self reduce];
}

This invokes the reduce method on the same object that sent the add: method (in this case, aFraction). We can remove the reduce line from the main() routine.

Class Methods

We can also write a class method that takes two Fractions, adds them, and returns a new Fraction. Look through the following code, and see if it makes sense.

Fraction.h

+ (Fraction *)addFraction:(Fraction *)frac1 toFraction:(Fraction *)frac2;

Fraction.m

+ (Fraction *)addFraction:(Fraction *)frac1 toFraction:(Fraction *)frac2 {
	Fraction *result = [[[Fraction alloc] init] autorelease];	// Store result of addition
																// Autorelease is memory management—
																// Don't worry about it now. 

	NSInteger resultNum = frac1.numerator * frac2.denominator + frac1.denominator * frac2.numerator;
	NSInteger resultDenom = frac1.denominator * frac2.denominator;

	[result setNumerator:resultNum overDenominator:resultDenom];
	[result reduce];
	
	return result;
}

FractionDemo.m

// Reset fractions
	[aFraction setNumerator:2 overDenominator:4];
	[bFraction setNumerator:1 overDenominator:3];
	
	NSLog(@"Using class method:");
	Fraction *classAddition = [Fraction addFraction:aFraction toFraction:bFraction];
	[classAddition display];

The result is the same: 5/6.

Source Download

In this version, in addition to adding everything mentioned above, the Fraction class has been cleaned up. Our old setNumerator: and setDenominator: methods have been removed; the synthesized properties fill in their role. Download here. For bonus points, try implementing the commented out method (making a Fraction out of a decimal value). Good luck!

Advertisements
Previous Post
Leave a comment

6 Comments

  1. Naphat

     /  July 8, 2011

    I tried to implement the fractionFromDecimal method, but can’t seem to do it. Can you post the solution?

    Reply
    • Well, there’s a reason it’s a bonus—there’s a bit more to it than you might think. See the post about floating-point values first. Because of the inherent inaccuracies of floating-point, you’ll have to do some estimation. Here’s one way: multiply the floating point value by a large power of ten, such as a billion. Convert (cast) the value to an integer, and create a fraction using that integer as the numerator, and the original large number as the denominator. Reduce the fraction, and you should get a fraction that is approximately equal to your original decimal. The larger the ‘large number’ is the better, but be careful of overflows!

      Reply
  2. objCnewbie

     /  November 30, 2012

    Hello! Awesome tutorial! I got stuck right before you started talking about Variable Scope. I kept getting errors on the implementation of (void)add:, saying the expected getter methods were unavailable for Fraction.

    So I went through the rest of this tutorial, then copied your downloadable code, and don’t get those errors.

    Do you know what was causing the errors?

    Cheers!

    Reply
  1. Learn Objective-C in 24 Days « Programming for iOS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Advertisements
  • Welcome

    My goal is to make CupsOfCocoa into a beautiful source for beginners to the iPhone platform to get started. Subscribe below for more, and stay tuned!

  • Contact Me

    If you need to contact me for any reason, feel free to send me an email.
  • The Giving Spirit

    If you've found this site helpful, would you consider donating a little sum? Any amount is appreciated...Thanks so much!

  • Roadmap

  • Enter your email address to follow this blog and receive notifications of new posts by email.

    Join 222 other followers

  • Back to the Past

    January 2011
    S M T W T F S
    « Dec   Feb »
     1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031  
  • Time Machine

  • You count!

    • 621,662 views
  • Worldwide Stats

    free counters
  • Advertisements
%d bloggers like this: