Classes and Objects
Objects form the heart of object-oriented programming. You define objects by building classes, which act as object-creation templates. In Objective-C, a class definition specifies how to build new objects that belong to the class. So to create a “widget” object, you define the Widget class and then use that class to create new objects on demand.
Each class lists its instance variables and methods in a public header file using the standard C .h convention. For example, you might define a Car object like the one shown in Listing 3-1. The Car.h header file shown here contains the interface that declares how a Car object is structured. Note that all classes in Objective-C should be capitalized.
Listing 3-1. Declaring the Car Interface (Car.h)
#import <Foundation/Foundation.h> @interface Car : NSObject { int year; NSString *make; NSString *model; } - (void) setMake:(NSString *) aMake andModel:(NSString *) aModel andYear: (int) aYear; - (void) printCarInfo; - (int) year; @end
In Objective-C, the @ symbol is used to indicate certain keywords. The two items shown here (@interface and @end) delineate the start and end of the class interface definition. This class definition describes an object with three instance variables: year, make, and model. These three items are declared between the braces at the start of the interface.
The year instance variable is declared as an integer (using int). Both make and model are strings, specifically instances of NSString. Objective-C uses this object-based class for the most part rather than the byte-based C strings defined with char *. As you see throughout this book, NSString offers far more power than C strings. With this class, you can find out a string’s length, search for and replace substrings, reverse strings, retrieve file extensions, and more. These features are all built into the base Cocoa Touch object library.
This class definition also declares three public methods. The first is called setMake:andModel:andYear:. This entire three-part declaration, including the colons, is the name of that single method. That’s because Objective-C places parameters inside the method name. In C, you’d use a function such as setProperties(char *c1, char *c2, int i). Objective-C’s approach, although heftier than the C approach, provides much more clarity and self-documentation. You don’t have to guess what c1, c2, and i mean because their use is declared directly within the name:
[myCar setMake:c1 andModel:c2 andYear:i];
The three methods are typed as void, void, and int. As in C, these refer to the type of data returned by the method. The first two do not return data; the third returns an integer. In C, the equivalent function declaration to the second and third method would be void printCarInfo() and int year();.
Using Objective-C’s method-name-interspersed-with-arguments approach can feel odd to new programmers but quickly becomes a much-loved feature. There’s no need to guess which argument to pass when the method name itself tells you what items go where. In Objective-C, method names are also interchangeably called “selectors.” You see this a lot in iOS programming, especially when you use calls to performSelector:, which lets you send messages to objects at runtime.
Notice that this header file uses #import to load headers rather than #include. Importing headers in Objective-C automatically skips files that have already been added. This lets you add duplicate #import directives to your various source files without any penalties.
Creating Objects
To create an object, you tell Objective-C to allocate the memory needed for the object and return a pointer to that object. Because Objective-C is an object-oriented language, its syntax looks a little different from regular C. Instead of just calling functions, you ask an object to do something. This takes the form of two elements within square brackets, the object receiving the message followed by the message itself:
[object message]
Here, the source code sends the message alloc to the Car class and then sends the message init to the newly allocated Car object. This nesting is typical in Objective-C.
Car *myCar = [[Car alloc] init];
The “allocate followed by init” pattern you see here represents the most common way to instantiate a new object. The class Car performs the alloc method. It allocates a new block of memory sufficient to store all the instance variables listed in the class definition, zeroes out any instance variables, and returns a pointer to the start of the memory block. The newly allocated block is called an “instance” and represents a single object in memory.
Some classes, like views, use specialized initializers such as initWithFrame:. You can write custom ones, such as initWithMake:andModel:andYear:. The pattern of allocation followed by initialization to create new objects holds universally. You create the object in memory and then you preset any critical instance variables.
Memory Allocation
In this example, the memory allocated is 16 bytes long. Both make and model are pointers, as indicated by the asterisk. In Objective-C, object variables point to the object itself. The pointer is 4 bytes in size. So sizeof(myCar) returns 4. The object consists of two 4-byte pointers, one integer, plus one additional field that does not derive from the Car class.
That extra field is from the NSObject class. Notice NSObject at the right of the colon next to the word Car in the class definition of Listing 3-1. NSObject is the parent class of Car, and Car inherits all instance variables and methods from this parent. That means that Car is a type of NSObject and any memory allocation needed by NSObject instances is inherited by the Car definition. So that’s where the extra 4 bytes come from.
The final size of the allocated object is 16 bytes in total. That size includes two 4-byte NSString pointers, one 4-byte int, and one 4-byte allocation inherited from NSObject. You can easily print out the size of objects using C’s sizeof function. This code uses standard C printf statements to send text information to the console. printf commands work just as well in Objective-C as they do in ANSI C.
NSObject *object = [[NSObject alloc] init]; Car *myCar = [[Car alloc] init]; // This returns 4, the size of an object pointer printf("object pointer: %d\n", sizeof(object)); // This returns 4, the size of an NSObject object printf("object itself: %d\n", sizeof(*object)); // This returns 4, again the size of an object pointer printf("myCar pointer: %d\n", sizeof(myCar)); // This returns 16, the size of a Car object printf("myCar object: %d\n", sizeof(*myCar));
Releasing Memory
In C, you allocate memory with malloc() or a related call and free that memory with free(). In Objective-C, you allocate memory with alloc and free it with release. (In Objective-C, you can also allocate memory a few other ways, such as by copying other objects.)
[object release]; [myCar release];
As discussed in Chapter 2, “Building Your First Project,” releasing memory is a little more complicated than in standard C. That’s because Objective-C uses a reference-counted memory system. Each object in memory has a retain count associated with it. You can see that retain count by sending retainCount to the object, although in the real world you should never rely on using retainCount in your software deployment. It’s used here only as a tutorial example to help demonstrate how a retain count works.
Every object is created with a retain count of 1. Sending release reduces that retain count by 1. When the retain count for an object reaches 0, or more accurately, when it is about to reach 0 by sending the release message to an object with a retain count of 1, it is released into the general memory pool.
Car *myCar = [[Car alloc] init]; // The retain count is 1 after creation printf("The retain count is %d\n", [myCar retainCount]); // This would reduce the retain count to 0, so it is freed instead [myCar release]; // This causes an error. The object has already been freed printf("Retain count is now %d\n", [myCar retainCount]);
Sending messages to freed objects will crash your application. When the second printf executes, the retainCount message is sent to the already-freed myCar. This creates a memory access violation, terminating the program. As a general rule, it’s good practice to assign instance variables to nil after the final release that deallocates the object. This prevents the FREED(id) error you see here when you access an already-freed object:
The retain count is 1 objc[10754]: FREED(id): message retainCount sent to freed object=0xd1e520
There is no garbage collection on iOS SDK. As a developer, you must manage your objects. Keep them around for the span of their use and free their memory when you are finished. Read more about basic memory management strategies later in this chapter.