An Overview of Objective-C Object Oriented Programming
So far in Objective-C 2.0 Essentials we have looked at the basics of programming in Objective-C such as variable types, flow control and looping. Although it would be possible to write a functional program using these techniques, there is much more to becoming a proficient Objective-C programmer. Objective-C is, above all, an object oriented programming language and as such any Objective-C programmer will be expected to create object-oriented applications using this language.
Aside from needing to learn about object oriented programming in order to create your own objects, you will also find that working with entities such as strings, arrays and files in Objective-C will also require an understanding of how to work with objects.
Objective-C provides extensive support for developing object-oriented applications. The subject area of object oriented programming is, however, large. It is not an exaggeration to state that entire books have be dedicated to the subject. As such, a detailed overview of object oriented software development is beyond the scope of this book. Instead, we will introduce the basic concepts involved in object oriented programming and then move on to explaining the concept as it relates to Objective-C application development.
What is an Object?
<google>ADSDAQBOX_FLOW</google> An object is a self-contained piece of functionality that can be easily used, and re-used as the building blocks for a software application.
Objects consist of data variables and functions (called methods) that can be accessed and called on the object to perform tasks. These are collectively referred to as members.
What is a Class?
Much as a blueprint or architect's drawing defines what an item or a building will look like once it has been constructed, a class defines what an object will look like when it is created. It defines, for example, what the methods will do and what the member variables will be.
Declaring an Objective-C Class Implementation
Before an object can be instantiated we first need to define the class 'blueprint' for the object. In this chapter we will create a Bank Account class to demonstrate the basic concepts of Objective-C object oriented programming.
An Objective-C class is defined in terms of an interface and an implementation. In the interface section of the definition we specify the base class from which the new class is derived and also define the members and methods that the class will contain. The syntax for the implementation section of a class is as follows:
@interface NewClassName: ParentClass {
- ClassMembers;
}
ClassMethods;
@end
The ClassMembers section of the implementation defines the variables that are to be contained within the class (also referred to as instance variables). These variables are declared in the same way that any other variable would declared in Objective-C.
The ClassMethods section defines the methods that are available to be called on the class. These are essentially functions specific to the class that perform a particular operation when called upon.
To create an example outline interface section for our BankAccount class, we would use the following:
@interface BankAccount: NSObject { } @end
The parent class chosen above is the NSObject class. This is a standard base class provided with the Objective-C Foundation Classes and is class from which most new class are derived. By deriving BankAccount from this parent class we inherit a range of additional methods used in creating, managing and destroying instances that we would otherwise have to write ourselves.
Now that we have the outline syntax for our class, the next step is to add some instance variables to it.
Adding Instance Variables to a Class
A key objective of object oriented programming is a concept referred to as data encapsulation. The idea behind data encapsulation is that data should be stored within classes and accessed only through methods defined in that class. Data encapsulated in a class are referred to as instance variables.
Instances of our BankAccount class will be required to store some data, specifically a bank account number and the balance currently held by the account. Instance variables are declared in the same way any other variables are declared in Objective-C. We can, therefore, add these variables as follows:
@interface BankAccount: NSObject { double accountBalance; long accountNumber; } @end
Having defined our instance variables, we can now move on to defining the methods of the class that will allow us to work with our instance variables while staying true to the data encapsulation model.
Define Class Methods
The methods of a class are essentially code routines that can be called upon to perform specific tasks within the context the an instance of that class.
Methods come in two different forms, class methods and instance methods. Class methods operate at the level of the class, such as creating a new instance of a class and are covered in more detail in Writing Objective-C Class Methods. Instance methods, on the other hand, operate only on the instance of a class (for example performing an arithmetic operation on two instance variables and returning the result). Class methods are preceded by a plus (+) sign in the declaration and instance methods are preceded by a minus (-) sign. If the method returns a result, the name of method must be preceded by the data type returned enclosed in parentheses. If a method does not return a result, then the must be declared as void. If data needs to be passed through the member (referred to as arguments), the member name is followed by a colon, the data type in parentheses and a name for the argument. For example, the declaration of a method to set the account number in our example might read as follows:
-(void) setAccountNumber: (long) y;
The method is an instance method so it is preceded by the minus sign. It does not return a result so it is declared as (void). It takes an argument (the account number) of type long so we follow the accountNumber name with a colon (:) specify the argument type (long) and give the argument a name (in this case we simply use y).
The following method is intended to return the current value of the account number instance variable (which is of type long):
-(long) getAccountNumber;
Methods may also be defined to accept more than one argument. For example to define a method that accepts both the account number and account balance we could declare it as follows:
-(void) setAccount: (long) y andBalance: (double) x;
Now that we have an understanding of the structure of method declarations within the context of the class interface definition, we can extend our BankAccount class accordingly:
@interface BankAccount: NSObject { double accountBalance; long accountNumber; } -(void) setAccount: (long) y andBalance: (double) x; -(void) setAccountBalance: (double) x; -(double) getAccountBalance; -(void) setAccountNumber: (long) y; -(long) getAccountNumber; -(void) displayAccountInfo; @end
Having defined the interface, we can now move on to defining the implementation of our class.
Declaring an Objective-C Class Implementation
The next step in creating a new class in Objective-C is to write the code for the methods we have already declared. This is performed in the @implementation section of the class definition. An outline implementation is structured as follows:
@implementation NewClassName
- ClassMethods
- ClassMethods
@end
In order to implement the methods we declared in the @interface section, therefore, we need to write the following:
@implementation BankAccount -(void) setAccount: (long) y andBalance: (double) x; { accountBalance = x; accountNumber = y; } -(void) setAccountBalance: (double) x { accountBalance = x; } -(double) getAccountBalance { return accountBalance; } -(void) setAccountNumber: (long) y { accountNumber = y; } -(long) getAccountNumber { return accountNumber; } -(void) displayAccountInfo { NSLog (@"Account Number %i has a balance of %f", accountNumber, accountBalance); } @end
We are now at the point where we can write some code to work with our new BankAccount class.
Declaring, Initializing and Releasing a Class Instance
So far all we have done is define the blueprint for our class. In order to do anything with this class, we need to create instances of it. The first step in this process is to declare a variable to store a pointer to the instance when it is created. We do this as follows:
BankAccount *account1;
Having created a variable to store a reference to the class instance, we can now allocate memory in preparation for initializing the class:
account1 = [BankAccount alloc];
In the above statement we are calling the alloc method of the BankAccount class (note that alloc is a class method inherited from the parent NSObject class, as opposed to an instance method created by us in the BankAccount class).
Having allocated memory for the class instance, the next step is to initialize the instance by calling the init instance method:
account1 = [account1 init];
For the sake of economy of typing, the above three statements are frquently rolled into a single line of code as follows:
BankAccount *account1 = [[BankAccount alloc] init];
In the first step of this section we allocated memory for the creation of the class instance. Good programming conventions dictate that memory allocated to a class instance should be released when the instance is no longer required. Failure to do so can result in memory leaks such that the application will continue to use up system memory until none is left. Those familiar with Java will be used to relying on the garbage collector to free up unused memory automatically. Some implementations of Objective-C also have a garbage collector but it is not implemented on all platforms (the iPhone being a case in point) so you should get into the practice of releasing allocated memory yourself:
[account1 release]
Calling Methods and Accessing Instance Data
Given the length of this chapter, now is probably a good time to recap what we have done so far. We have now created a new class called BankAccount. Within this new class we declared some instance variables to contain the bank account number and current balance together with some instance methods used to set, get and display these values. In the preceding section we covered the steps necessary to create and initialize an instance of our new class. The next step is to learn how to call the instance methods we built into our class.
The syntax for invoking methods is to place the instance variable name and method to be called in square brackets ([]). For example, to call the displayAccountInfo method on the instance of the class we created previously we would use the following syntax:
[account1 displayAccountInfo];
When the method accepts a single argument, the method name is followed by a colon (:) followed by the value to be passed to the method. For example, to set the account number:
[account1 setAccountNumber: 34543212];
In the case of methods taking multiple arguments (as is the case with our setAccount method) syntax similar to the following is employed:
[account1 setAccount: 4543455 andBalance: 3010.10];
Creating the Program Section
The last stage in this exercise is to bring together all the components we have created so that we can actually see the concept working. The last section we need to look at is called the program section. This is where we write the code to create the class instance and call the instance methods. Most Objective-C programs have a main() routine which is the start point for the application. The following sample main routine creates an instance of our class and calls the methods we created:
int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; BankAccount *account1; // Create a variable to point to our class instance account1 = [BankAccount alloc]; // Allocate memory for class instance account1 = [account1 init]; // Initialize the instance [account1 setAccountBalance: 1500.53]; // Set the account balance [account1 setAccountNumber: 34543212]; // Set the account number [account1 displayAccountInfo]; // Call the method to display the values of the instance variables [account1 setAccount: 4543455 andBalance: 3010.10]; // Set both account number and balance // Output values using the getter methods NSLog(@"Number = %i, Balance = %f", [account1 getAccountNumber], [account1 getAccountBalance]); [pool drain]; return 0; }
Bringing it all Together
Our example is now complete so lets bring all the components together:
#import <Foundation/Foundation.h> // Interface Section Starts Here @interface BankAccount: NSObject { double accountBalance; long accountNumber; } -(void) setAccount: (long) y andBalance: (double) x; -(double) getAccountBalance; -(long) getAccountNumber; -(void) setAccountBalance: (double) x; -(void) setAccountNumber: (long) y; -(void) displayAccountInfo; @end // Implementation Section Starts Here @implementation BankAccount -(void) setAccount: (long) y andBalance: (double) x; { accountBalance = x; accountNumber = y; } -(void) setAccountBalance: (double) x { accountBalance = x; } -(double) getAccountBalance { return accountBalance; } -(void) setAccountNumber: (long) y { accountNumber = y; } -(long) getAccountNumber { return accountNumber; } -(void) displayAccountInfo { NSLog (@"Account Number %i has a balance of %f", accountNumber, accountBalance); } @end // Program Section Starts Here int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; BankAccount *account1; account1 = [BankAccount alloc]; account1 = [account1 init]; [account1 setAccountBalance: 1500.53]; [account1 setAccountNumber: 34543212]; [account1 displayAccountInfo]; [account1 setAccount: 4543455 andBalance: 3010.10]; NSLog(@"Number = %i, Balance = %f", [account1 getAccountNumber], [account1 getAccountBalance]); [pool drain]; return 0; }
When the above code is saved, compiled and executed we should expect to see the following output:
2009-10-14 14:44:06.634 t[4287:10b] Account Number 34543212 has a balance of 1500.530000 2009-10-14 14:44:06.635 t[4287:10b] Number = 4543455, Balance = 3010.100000
Structuring Object-Oriented Objective-C Code
Our example is currently contained within a single source file. In practice, the convention is to place the interface and implementation in their own include files that are then included in the program source file and complication. Generally the interface section is contained within a file called ClassName.h where ClassName is the name of the class. In our case, we would create a file called BankAccount.h containing the following:
#import <Foundation/Foundation.h> @interface BankAccount: NSObject { double accountBalance; long accountNumber; } -(void) setAccount: (long) y andBalance: (double) x; -(double) getAccountBalance; -(long) getAccountNumber; -(void) setAccountBalance: (double) x; -(void) setAccountNumber: (long) y; -(void) displayAccountInfo; @end
Next, the implementation section goes in a file traditionally named ClassName.m where ClassName once again is refers to the name of the class. For example, BankAccount.m will contain the following (note that it is necessary to import the BankAccount.h file into this file):
#import "BankAccount.h" @implementation BankAccount -(void) setAccount: (long) y andBalance: (double) x; { accountBalance = x; accountNumber = y; } -(void) setAccountBalance: (double) x { accountBalance = x; } -(double) getAccountBalance { return accountBalance; } -(void) setAccountNumber: (long) y { accountNumber = y; } -(long) getAccountNumber { return accountNumber; } -(void) displayAccountInfo { NSLog (@"Account Number %i has a balance of %f", accountNumber, accountBalance); } @end
Finally, we will create our program file and call it bank.m (though any suitable name will do as long as it has a .m filename extension). This file also needs to import our interface file (BankAccount.h):
#import "BankAccount.h" int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; BankAccount *account1; account1 = [BankAccount alloc]; account1 = [account1 init]; [account1 setAccountBalance: 1500.53]; [account1 setAccountNumber: 34543212]; [account1 displayAccountInfo]; [account1 setAccount: 4543455 andBalance: 3010.10]; NSLog(@"Number = %i, Balance = %f", [account1 getAccountNumber], [account1 getAccountBalance]); [pool drain]; return 0; }
With the files created it is time to compile the code. When doing so we need to compile both the main.m and BankAccount.m files. The command to achieve this on a Mac OS X system is as follows:
gcc -framework Foundation bankAccount.m bank.m -o bank
Once compiled, the bank executable can be run.