The Basics of Object Oriented Programming in Objective-C (iPad iOS 6)
Previous | Table of Contents | Next |
The Basics of Objective-C Programming | Objective-C - Data Encapsulation, Synthesized Accessors and Dot Notation |
Learn SwiftUI and take your iOS Development to the Next Level |
Objective-C provides extensive support for developing object-oriented iOS iPad applications. The subject area of object oriented programming is, however, large. It is not an exaggeration to state that entire books have been 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. Once again, whilst we strive to provide the basic information you need in this chapter, we recommend reading a copy of Objective-C 2.0 Essentials if you are unfamiliar with Objective-C programming.
What is an Object?
Objects are self-contained modules 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.
Creating the Example Project
For the purposes of this chapter a new Xcode project will be required. Begin, therefore, by launching Xcode and selecting the option to Create a New Project. In the new project panel, select the Application option listed beneath OS X in the left hand pane, followed by Command Line Tool in the main pane before clicking on Next. Enter BankAccount as the product name, verify that the Type menu is set to Foundation and that Automatic Reference Counting is enabled. Click the Next button, choose a suitable file system location for the project files and then click Create.
Declaring an Objective-C Class Interface
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 to be derived and also define the members and methods that the class will contain. The syntax for the interface section of a class is as follows:
@interface NewClassName: ParentClass { // Instance Variables } // Method Declarations @end
The Instance Variables section of the interface defines the variables that are to be contained within the class. These variables are declared in the same way that any other variable would be declared in Objective-C.
The Method Declarations section, on the other hand, 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 framework and is the class from which most new classes 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.
The next step is to add the new class to our Xcode project. This can be achieved by Ctrl-clicking on the BankAccount entry located at the top of the Project Navigator panel and selecting New File… from the menu. Alternatively, select File -> New -> File… from the Xcode menu bar at the top of the screen. Once invoked, the new file panel illustrated in Figure 9-1 will appear:
Figure 9-1
Since we are adding a new class to the project, select the Objective-C class entry before clicking Next. On the subsequent panel, enter BankAccount into the Class field and change the Subclass of menu to NSObject. Click Next, select a location for the new class files and click on Create.
A review of the files in the Project Navigator panel will reveal that Xcode has added both interface (BankAccount.h) and implementation (BankAccount.m) files to the project. Select the BankAccount.h file and note that it contains the outline of the new class:
#import <Foundation/Foundation.h> @interface BankAccount : NSObject @end
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 (commonly abbreviated to ivars).
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.
Defining Instance Methods
The methods of a class are essentially code routines that can be called upon to perform specific tasks within the context of 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. 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 the method must be preceded by the data type returned enclosed in parentheses. If a method does not return a result, then the method must be declared as void. If data needs to be passed through to the method (referred to as arguments), the method 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. Within Xcode, select the BankAccount.h interface file and modify it to add method declarations:
@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 are ready to 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 // Methods @end
In order to implement the methods we declared in the @interface section, therefore, we need to modify the BankAccount.m implementation file as follows:
#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 %ld 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 and Initializing 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 class method:
account1 = [account1 init];
For the sake of economy of typing, the above three statements are frequently 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, notably iOS up to and including iOS 6. Fortunately the iOS 5 SDK introduced Automatic Reference Counting (ARC) which provides a mechanism that avoids the necessity to manually release memory in your code. If you are developing on a platform without ARC implemented, you should get into the practice of releasing allocated memory yourself:
[account1 release];
If, on the other hand, you are using Xcode 4.2 or later there is no need to manually release objects unless you specifically disable ARC support.
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 object pointer 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];
How Variables are Stored
When we declare a variable in Objective-C and assign a value to it we are essentially allocating a location in memory where that value is stored. Take, for example, the following variable declaration:
int myvar = 10;
When the above code is executed, a block of memory large enough to hold an integer value is reserved in memory and the value of 10 is placed at that location. Whenever we reference this variable in code, we are actually using the variable value. For example, the following code adds the value of myvar (i.e. 10) to the constant value 20 to arrive at a result of 30.
int result = 20 + myvar;
Similarly, when we pass a variable through as an argument to a method or function we are actually passing the value of the variable, not the variable itself. To better understand this concept, consider the following sample program:
#import <Foundation/Foundation.h> void myFunction(int i) { i = i + 10; } int main (int argc, const char * argv[]) { @autoreleasepool { int myvar = 10; NSLog (@"Before call to function myvar = %i", myvar); myFunction (myvar); NSLog (@"After call to function myvar = %i", myvar); } return 0; }
The above program consists of a main function that declares our myvar variable and displays the current value. It then calls the function myFunction passing through the value of the myvar variable. The myFunction function adds 10 to the value it was passed as an argument and then returns to the main function where the value of myvar is once again displayed. When compiled and executed the following output is displayed:
Before call to function myvar = 10 After call to function myvar = 10
Clearly, even though the value passed through to myFunction was increased by 20 the value of myvar remained unchanged. This is because what was passed through as an argument to myFunction was the value of myvar, not the myvar variable itself. Therefore, in myFunction we were simply working on a constant value of 10 that had absolutely no connection to the original myvar variable.
In order to be able to work on the actual variable in the function we need to use something called indirection.
An Overview of Indirection
Indirection involves working with pointers to the location of variables and objects rather than the contents of those items. In other words, instead of working with the value stored in a variable, we work with a pointer to the memory address where the variable is located.
Pointers are declared by prefixing the name with an asterisk (*) character. For example to declare a pointer to our myvar variable we would write the following code:
int myvar = 10; int *myptr;
In the above example we have declared our myvar variable and then declared a variable named myptr as being of type pointer to an integer. Having declared both our variable and our pointer we now need to assign the address of our variable to the pointer. The address of a variable is referenced by prefixing it with the ampersand (&) character. We can, therefore, extend our example to assign the address of the myvar variable to the myptr variable:
int myvar = 10; int *myptr; myptr = &myvar;
We now have now implemented a level of indirection by creating a pointer to our variable. As such, we can now pass this pointer through as an argument to our function such that we will be able to work on the actual variable, rather than just the value (10) of the variable. In order to access the value of a variable using a pointer to that variable, we prefix the pointer variable name with an asterisk (*). When we do this we are telling the compiler we want to work with the contents of the variable or object at the memory address contained within the pointer:
int myvar = 10; int *myptr; myptr = &myvar; *myptr = *myptr + 15;
Similarly, we can modify our function to accept a pointer to an integer and perform the addition on that variable. As such, we can now modify our previous program as follows:
#import <Foundation/Foundation.h> void myFunction(int *i) { *i = *i + 10; } int main (int argc, const char * argv[]) { @autoreleasepool { int myvar = 10; int *myptr; myptr = &myvar; NSLog (@"Before call to function myvar = %i", myvar); myFunction (myptr); NSLog (@"After call to function myvar = %i", myvar); } return 0; }
Now because we are passing through a pointer to myvar when we call the function and have modified the function to work with the contents of the variable, the output clearly indicates that the function changed the value of myvar when it was called. We have, therefore, just used indirection.
Before call to function myvar = 10 After call to function myvar = 20
Indirection and Objects
So far in this chapter we have used indirection with a variable. The same concept applies for objects. In this chapter we have worked with our BankAccount class. When doing so we wrote statements similar to the following:
BankAccount *account1; BankAccount *account1 = [[BankAccount alloc] init];
The first line of code (BankAccount *account1;) is actually declaring that the variable named account1 is a pointer to an object of type BankAccount. We are, therefore, using indirection to provide a handle to our object. The calls to the alloc and init methods subsequently create the object in memory and the assign the address of that object to the account1 pointer variable. We are, therefore, using indirection once again.
One key point to note is that we do not need to prefix the object pointer with a * when perform operations such as calling methods. For example, we can call a method on our account1 object without using an asterisk:
[account1 displayAccountInfo];
Indirection and Object Copying
Due to the fact that references to objects utilize indirection it is important to understand that when we use the assignment operator (=) to assign one object to another we are not actually creating a copy of the object. Instead, we are creating a copy of the pointer to the object. Consider, therefore, the following code:
BankAccount *account1; BankAccount *account2; BankAccount *account1 = [[BankAccount alloc] init]; account2 = account1;
In the above example, we will end up with two pointers (account1 and account2) that point to the same location in memory.
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. Based on the selections made when the BankAccount project was created, Xcode has already created a template main() routine contained in the file named main.m. Select this file in the Project Navigator panel and modify it as follows, making sure to import the BankAccount.h file interface file:
#import <Foundation/Foundation.h> #import "BankAccount.h" int main(int argc, const char * argv[]) { @autoreleasepool { // Create variable to point to the class instance BankAccount *account1; // Allocate memory for instance account1 = [BankAccount alloc]; // Initialize the instance account1 = [account1 init]; // Set the account balance [account1 setAccountBalance: 1500.53]; // Set the account number [account1 setAccountNumber: 34543212]; // Call the method to display the values of // the instance variables [account1 displayAccountInfo]; // Set both account number and balance [account1 setAccount: 4543455 andBalance: 3010.10]; // Output values using the getter methods NSLog(@"Number = %ld, Balance = %f", [account1 getAccountNumber], [account1 getAccountBalance]); } return 0; }
Compiling and Running the Program
In order to test the application in Xcode, simply click on the Run button in the toolbar. When the program has executed, the following output should appear in the Xcode console:
2012-11-16 14:44:06.634 t[4287:10b] Account Number 34543212 has a balance of 1500.530000 2012-11-16 14:44:06.635 t[4287:10b] Number = 4543455, Balance = 3010.100000
Summary
Objective-C is, as the name in part suggests, an object oriented programming language. Object oriented programming advocates an approach whereby the code that comprises an application is structured in the form of re-usable software components (referred to as objects) that can be assembled and re-used to implement the required functionality.
The goal of this chapter has been to introduce the basic concepts of object oriented programming, whilst working through the creation of an example program designed to demonstrate this concept in action within the context of the Objective-C programming language.
Learn SwiftUI and take your iOS Development to the Next Level |
Previous | Table of Contents | Next |
The Basics of Objective-C Programming | Objective-C - Data Encapsulation, Synthesized Accessors and Dot Notation |