Advertisement

Learn Objective-C: Day 4

by
This post is part of a series called Learn Objective-C.
Learn Objective-C: Day 3
Learn Objective-C: Day 5

Welcome to part four of this series on Objective-C. So far, we've looked a lot at theory and the principles and functionality of the language to get a good idea of how it works. Today, we will be making a simple class similar to the car example we looked at in previous parts of this series. Our class will take the details of a car, allowing us to get and set the values held. After today's example you should be able to create your own classes in Xcode and toy around with them.

So far, we have had some great feedback via email, twitter and comments. Itʼs great to see so many people are interested in this subject and itʼs even better to see that so many of you are trying it out for yourself and asking some great questions. Keep it up!

Getting Started

Start by firing up Xcode and creating a new project. Under the Mac OS X separator, click Application, then click Command Line Tool. Finally, change the drop down box to set the type to Foundation.

Xcode New Project

Save the project as whatever you want, I called mine CarApp. Once the project window appears, we need to create a new class. Hit Command-N (or File > New File), navigate to Cocoa Class under Mac OS X and select Objective-C class. Make sure Subclass of is set to NSObject and press Next. Name your class SimpleCar and ensure that a .h file will be created, then save it.

Our class now exists, but it does nothing. Let's change that by giving it some code. Remember that in Objective-C we split our code into two parts: interface and implementation. It makes logical sense to work on the interface first, so that's where we'll start.

Coding the Interface

Open up the SimpleCar.h file and in its current state it should look like this (I've omitted the comment header from below)

#import <Cocoa/Cocoa.h>


@interface SimpleCar : NSObject {

}

@end

First of all, we're including Cocoa.h, which gives us access to such things as NSString, NSMutableString, etc. Then, we create our class (SimpleCar) as a subclass of NSObject.

Now we need to decide on what information our class needs to store. Since we're using a car as our example we need to store car-related information, such as:

  • Make
  • Model
  • VIN

There's a lot more we could go into, but for now that will do. For each of these properties, we need to store them in a variable suited for that type of data. Make and model will be a range of characters (such as text, number and possibly punctuation) so it makes sense to use a string. The VIN (Vehicle Identification Number) will only be a number so that's what we'll use. Our code now looks like this (header omitted):

@interface SimpleCar : NSObject {
	NSString* make;
	NSString* model;
	NSNumber* vin;
}

@end

We previously said that in order to get or set data from a class, a method should be used. So to set the variables, we need to add methods. To do this, we'll make four: one will set the make, one the model, one the VIN, and a final method will set both make AND model (just to show you how to use multiple arguments).

@interface SimpleCar : NSObject {
	NSString* make;
	NSString* model;
	NSNumber* vin;
}

// set methods
- (void) setVin:   (NSNumber*)newVin;
- (void) setMake:  (NSString*)newMake;
- (void) setModel: (NSString*)setModel;

// convenience method
- (void) setMake: (NSString*)newMake 
        andModel: (NSString*)newModel;

@end

We declare methods after the curly bracket and before @end. By placing a dash (minus sign) before the method, we tell the compiler we're about to declare an instance method. An instance method is a method executed on our instance. Conversely, a plus sign indicates that the method being invoked is a class method that does not need an individual object instance to execute -more on this later.

Our first method returns void, is called setVin and takes an NSNumber as an argument. Our second method is similar, it returns void, is call setMake, and takes an NSString as an argument. The third is the same, with a different name.

Our final method also returns void but takes two parameters: newMake and newModel, both of which should be NSString. The naming used in this method is similar to how most Objective-C methods are named: in plain English. So when you read the method allowed it's obvious that the method will "Set make and model." It's important to remember that the method name in this case is 'setMake:andModel:' - all the argument titles are included in the method name.

An important note is that we use (void) because our methods do not need to return anything. Since all they are doing is setting data and do not need to return anything back (such as a success message) we simply use void.

Next, we will add the methods we will use to get the values. Although we call our methods get and set methods, we only usually use "set" in the title and omit "get." How you name your methods is ultimately up to you, but dropping "get" is common and helps avoid confusion.

Our new set of methods looks like this:

// set methods
- (void) setVin:   (NSNumber*)newVin;
- (void) setMake:  (NSString*)newMake;
- (void) setModel: (NSString*)newModel;

// convenience method
- (void) setMake: (NSString*)newMake 
        andModel: (NSString*)newModel;

// get methods
- (NSString*) make;
- (NSString*) model;
- (NSNumber*) vin;

Notice that the get methods use the same names as the variables in the class. This will make it simple when we fetch the variables. It will be as if we're accessing the variables directly, essentially making the get methods appear transparent.

Coding the Implementation

So now that the interface is in place and we know what the class will do, we need to implement our methods. Looking back, we have four methods we need to implement: setVin, setMake, setModel and setMake:andModel. Before we move files, copy the method declarations to your clipboard (Cmd+C). Now close SimpleCar.h and fire up SimpleCar.m in the editor, pasting the method declarations in between the @implementation and @end, like so:

@implementation SimpleCar

// set methods
- (void) setVin:   (NSNumber*)newVin;
- (void) setMake:  (NSString*)newMake;
- (void) setModel: (NSString*)newModel;

// convenience method
- (void) setMake: (NSString*)newMake 
        andModel: (NSString*)newModel;

// get methods
- (NSString*) make;
- (NSString*) model;
- (NSNumber*) vin;


@end

Obviously this isn't right, so what we need to do is swap the semi-colons for curly brackets where the inner workings of the method will go, like this:

@implementation SimpleCar

// set methods
- (void) setVin: (NSNumber*)newVin {

}

- (void) setMake: (NSString*)newMake {

}

- (void) setModel: (NSString*)newModel {

}

- (void) setMake: (NSString*)newMake 
        andModel: (NSString*)newModel {

}

// get methods
- (NSString*) make {

}

- (NSString*) model {

}

- (NSNumber*) vin {

}

@end

Now we need to give our methods some code. Let's start with the getter methods as they're straightforward enough. For each getter method, all we need to do is make sure that the function returns what it is intended to return. For this reason, our getter methods look like this:

- (NSString*) make {
    return make;
}

- (NSString*) model {
    return model;
}

- (NSNumber*) vin {
    return vin;
}

Remember: the methods are returning the variables we defined in the interface file. Don't get confused between the method names and the variable names.

That's pretty straightforward, when we call make (for example), then make returns the pointer to an NSString - in this case to the make variable. The same happens for model and vin (except of course vin returns a number).

Now for the setter methods, first we'll look at the code and then we'll go through it afterwards. Our setter methods look like this:

// set methods
- (void) setVin: (NSNumber*)newVin {
	
    [vin release];
    vin = [[NSNumber alloc] init];
    vin = newVin;
	
}

- (void) setMake: (NSString*)newMake {
	
    [make release];
    make = [[NSString alloc] initWithString:newMake];
	
}

- (void) setModel: (NSString*)newModel {

    [model release];
    model = [[NSString alloc] initWithString:newModel];

}

// convenience method
- (void) setMake: (NSString*)newMake 
        andModel: (NSString*)newModel {
	
    // Reuse our methods from earlier
    [self setMake:newMake];
    [self setModel:newModel];
	
}

The set methods are a bit trickier than our get methods. We want to alloc the values that are passed into each method so that they are owned by the class. We first release these variables in case they are already alloc'd. If they are not alloc'd, then they are nil, and nil objects ignore messages passed to them. We will cover these issues more when we discuss memory management.

Because we actually allocated memory for our objects in the setter methods, we need to be sure we release them when the object is released from memory. To do this, we need to add a custom dealloc method, like so:

-(void) dealloc
{
    [vin release];
    [make release];
    [model release];
    [super dealloc];
}

Testing the Class

Congratulations! If you followed everything above then you should now have a working class (if not, download the source files available with this article). So, let's test it out.

Open up the main file of your project (mine is called CarApp.m) which by default should look something like this:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // Insert custom code here...
    NSLog(@"Hello, World!");
    
    [pool drain];
    return 0;
}

Delete the comment and NSLog line as we won't be needing them right now.

In order to begin using our class, we need to pull it into the program. Underneath the original #import line add the following line:

#import "SimpleCar.h"

Our class is now available for use, but we need to create an instance of it in order to test it out. Here's the code used in total:

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

int main (int argc, const char * argv[]) {

  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    SimpleCar *myCar = [[SimpleCar alloc] init];
	
    NSNumber *newVin = [NSNumber numberWithInt:123];
	
    [myCar setVin:newVin];
    [myCar setMake:@"Honda" andModel:@"Civic"];
	
    NSLog(@"The car is: %@ %@", [myCar make], [myCar model]);
    NSLog(@"The vin is: %@", [myCar vin]);
	
    [myCar release];
	
  [pool drain];

  return 0;
}

First of all we create a pointer to an instance of SimpleCar called myCar. Next we use alloc and init - these will be discussed later on down the line.

Next, since we need to pass an NSNumber to the setVin method, we make one here. Again we create a pointer to an NSNumber instance called newVin and we initiate it with the integer value of 123. The constant '123' is an integer, which is why we use numberWithInt.

Next, we invoke our methods, first of all we put who should receive the message (myCar) and then we use the method setVin. After the colon is the value we are supplying to the method which is the NSNumber we created before. Next we do the same but call the setMake method with two parameters. The reason these parameters are preceded by an @ sign is to tell the compiler that the following is a string.

Finally, we release myCar as we are done with it - more on this later in the series under memory management.

Our class is now working, and in order to see the proof, we added some NSLog statements to print the values to the console. If you open up the console (Run > Console) and then build and run your app, you should see output similar to this:

Debugger

Property and Synthesize

If you look at the code above, a lot of it seems quite pointless and excessive. For example, in our getter methods all we are doing is returning an instance variable - but this takes up three lines of code to do something simple. Also, in our setter methods, we are just setting instance variables - essentially all of our methods, except our method that takes two arguments, seem bloated and in the way. Objective-C solves this with @property and @synthesize, which replace our accessor methods and make for much neater coding.

This is what our new interface file looks like using properties:

#import 

@interface SimpleCar : NSObject {
	NSString* make;
	NSString* model;
	NSNumber* vin;
}

@property(readwrite, retain) NSString* make;
@property(readwrite, retain) NSString* model;
@property(readwrite, retain) NSNumber* vin;

// convenience method
- (void) setMake: (NSString*)newMake 
        andModel: (NSString*)newModel;

@end

Wow, that really is a lot shorter. So what's happening with the @property declarations? First we tell the compiler we are declaring a property by using @property, then we follow with attributes for this property. The attributes are the read/write status of a property and some memory management. We have used readwrite for all, which means getter and setter methods are dynamically created for our instance variables (we could of used writeonly or readonly for just one or the other). The reason we use retain will become clear next time when we cover memory management.

Before this can work, we need to implement it in our implementation file, we do this using @synthesize. Our new implementation file looks like this:

#import "SimpleCar.h"

@implementation SimpleCar

@synthesize make, model, vin;

- (void) setMake: (NSString*)newMake 
        andModel: (NSString*)newModel {
	
    [self setMake:newMake];
    [self setModel:newModel];

}

@end

Doesn't that look better? Think of it like this, @property replaces all of the interface method declarations for getters and setters, and @synthesize replaces the actual methods themselves. The getters and setters are now dynamically created and we don't need to waste time creating them unless we need to do something really special.

Wrapping Up

You should now have a firm grip of classes, objects and instances. Sure, you're not creating classes that will change the world yet, but this stuff takes time. It's better to learn by example, so if you're not coding as you go along then be sure to at least download the source files and have a read through (and a compile) to ensure you're 100% on what's going on.

Next Time

We've mentioned memory management a lot in this tutorial, it's a very important subject that needs to be addressed (pun intended), so we'll dive in to that next time. True, it isn't the most fun subject or the easiest to come to terms with, but it's absolutely crucial if you want to become a skilled Objective-C programmer.

Challenge

This week's challenge may be a little tricky, but we'll see how you get on. First of all, if you haven't copied all the code above, download the source files that are included with this article. The challenge is to add another class to the project, but this time it should be a subclass of SimpleCar (remember, we define the parent class in the interface file). If you can do that, play around and use the inherited methods and try to add your own for things such as: engine size, doors or height.

Remember: if you have any questions or queries, drop a comment below or shoot me a message on Twitter. The only stupid question is the one you didn't ask - this series is about learning so feel free to ask away!

Advertisement