Chapter 3
Just Enough Objective-C: Part One

Key Skills & Concepts

• Understanding Objective-C classes and objects

• Understanding an interface and an implementation

• Understanding simple messaging

• Understanding alloc and init

• Managing memory using retain and release

• Managing memory using autorelease

iOS applications use Cocoa classes, and these classes use the Objective-C programming language. So you must know Objective-C if you wish to program iOS devices. At first glance, Objective-C’s syntax might seem strange and difficult. But don’t worry—the language is easy and its strangeness will give way to an elegance I’m sure you will appreciate. In this and the next chapter you learn enough Objective-C to begin iOS programming.

CAUTION
If coming from a .NET or Java background, pay particular attention to the sections on memory management. Unlike these languages, memory management is not automatic on iOS devices. You must manage memory manually.

Objective-C Classes and Objects

Objective-C classes are the same as classes in any other object-oriented programming language. A class encapsulates both state (properties) and behavior (methods), and forms an object-oriented program’s basic building blocks. An object-oriented application functions by objects sending messages between each other. For instance, in a typical Java command-line application, you begin the program by calling a static method called main in a class. This main method instantiates one or more objects, and the application’s remaining functionality consists of messages between those objects instantiated in the main method, as well as any objects they might in turn instantiate.

Class Interface and Implementation

Objective-C separates a class into an interface and an implementation. An interface declares instance variables and methods. It is a standard C header file and doesn’t provide any method definitions. The implementation contains the method definitions for the class. It is a file with its own .m extension rather than a .c extension.

Try This: Generating an Objective-C Class’ Interface and Implementation

1. Create a new View-based Application. Only this time, rather than selecting iPhone from the Product drop-down, select iPad. Name the project ChapThree.

2. In Groups & Files, right-click Classes and select New Group from the pop-up menu. Name the group Objective-C.

3. Right-click the newly created Objective-C folder and select New File from the pop-up menu. From the New File dialog, highlight Cocoa Touch and select Objective-C Class. Ensure the Subclass says NSObject (Figure 3-1). Click Next.

4. On the next dialog screen, name the class Simple.

The template generates Simple for you, writing its interface in Simple.h (Listing 3-1) and its implementation in Simple.m (Listing 3-2).

Image

Figure 3-1 Selecting a new Objective-C class using Xcode’s New File dialog

Listing 3-1 Objective-C interface

#import <Foundation/Foundation.h>
@interface Simple : NSObject {
}
@end

Listing 3-2 Objective-C implementation

#import "Simple.h"
@implementation Simple
@end

The @interface and @implementation Compiler Directives

In Simple.h, note the @interface compiler directive. In the Simple.m file, note the @ implementation compiler directive. These directives distinguish a class’ interface from its implementation. Code within the @interface and @end compiler directives in Simple.h makes up Simple’s interface, while code within the @implementation and @end compiler directives makes up Simple’s implementation.

Method Declaration and Definition

You declare a class’ methods and instance variables in its interface. You define a class’ methods and instance variables in its implementation. Declaring a method means you tell the compiler that a class will have a method, with a certain signature, but you don’t provide the actual code for the method. For instance, consider the following method declaration.

-(void) sayHello: (NSString*) name;

The declaration tells the compiler to expect a method called sayHello that returns nothing (void) and takes an NSString as an argument. The declaration says nothing about the method’s content.

You provide the compiler with a method’s implementation by defining the method. Defining a method means you provide a method declaration’s actual behavior, or its implementation. For instance, the sayHello method in Listing 3-3 provides the sayHello method declaration’s behavior.

Listing 3-3 A simple Objective-C method implementation

-(void) sayHello: (NSString*) name {
  NSMutableString *message = [[NSMutableString alloc]
        initWithString:@"Hello there "];
  [message appendString:name];
  NSLog(message);
  [message release];
}

Try This: Adding SayHello to the Simple Class

1. Open the last section’s project, ChapThree. Add the sayHello method from Listing 3-3 to Simple.m (Listing 3-4). Don’t forget to add the method’s declaration to Simple.h (Listing 3-5).

Listing 3-4 Simple.m modified to declare sayHello

#import "Simple.h" @implementation Simple
-(void) sayHello: (NSString *) name {
NSMutableString *message = [[NSMutableString alloc] initWithString:
@"Hello there "];
  [message appendString:name];
  NSLog(message);
  [message release];
}
@end

Listing 3-5 Simple.h modified to declare sayHello

#import <Foundation/Foundation.h>
@interface Simple : NSObject {
}
-(void) sayHello: (NSString *) name;
@end

2. Open main.m in the Other Sources folder and import Simple. Then in main, create a Simple instance and call it the sayHello method (Listing 3-6).

Listing 3-6 The file main.h modified to call the sayHello method

#import <UIKit/UIKit.h>
#import "Simple.h"
int main(int argc, char *argv[]) {
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  Simple * mySimple = [[Simple alloc] init];
  [mySimple sayHello:@"James"];
  [mySimple release];
  int retVal = UIApplicationMain(argc, argv, nil, nil);
  [pool release];
  return retVal;
}

3. Build and run the program, and the hello message will appear in the debugger console.

Interface Anatomy

A class’ interface consists of import statements, a class declaration, any instance variables, and method signatures. Review Simple’s interface in the Simple.h file. Objective-C classes import or include other libraries and headers just like C (just a reminder, always use import, as this assures you won’t include the header file twice). The following line declares to the compiler an Objective-C class named Simple that extends NSObject:

@interface Simple : NSObject

Opening and closing braces follow the class declaration. Instance variables go between these braces. Below the closing brace, you add class method declarations. Following any method declarations, the interface ends with the @end directive, which signifies the interface’s end. Figure 3-2 summarizes an Objective-C interface’s anatomy.

Implementation Anatomy

An interface is only half an Objective-C class, though. A class’ implementation is as important as its interface. Review Simple’s implementation in the Simple.m file. This file begins by importing the class’ interface. Simple’s implementation then begins with the @implementation compiler directive.

@implementation Simple

Simple’s implementation ends with the @end compiler directive. Method definitions go between the two directives. Figure 3-3 summarizes an Objective-C class implementation.

Image

Figure 3-2 An Objective-C interface summary

Image

Figure 3-3 An Objective-C implementation summary

Public, Private, and Protected Instance Variables

Classes can set instance variables to be private, protected, and public. You use the compiler directives @private, @protected, and @public to declare instance variable visibility. The private directive ensures variables marked as private are only visible to the class that declares the instance variable. The protected directive ensures protected variables are only visible to the declaring class and its descendants. The public directive allows any class access to the public variables.

Consider the interface code snippet in Listing 3-7.

Listing 3-7 Public and private methods

@public
  NSString* groupName;
  int intGroupSize;
@private
  NSString* otherGroupName;
  int intOtherGroupSize;

In this interface declaration, the instance variables groupName and intGroupSize are public, while otherGroupName and intOtherGroupSize are private.

Understanding Simple Messaging

Objective-C methods look substantially different from Java methods. Although the syntax is confusing at first, it’s not difficult once you become used to it. Note that you don’t say that you “call a method” when using Objective-C. Instead, you “send a message to a receiver.” For instance, using Java you might type the following:

objMyObject.getFooUsingID(33);

When describing this line, I write that I am calling objMyObject’s getFoo method and passing the argument 33. In Objective-C, the same message appears as follows:

[objMyObject getFooUsingID : 33];

When describing this line, I write that I am sending a getFooUsingID message, passing the argument 33, and objMyObject is the receiver.

The difference between calling a method and sending a message isn’t Objective-C’s only difference from Java, C++, and other dot-notation languages. Objective-C uses what’s called infix notation. Infix notation mixes operands and operators. You don’t really need to fully understand infix notation, other than it means Objective-C looks substantially different from Java and C++. An Objective-C message begins with an opening square brace and ends with a closing square brace followed by a semicolon. The object’s name follows the opening brace, followed by a space, followed by the message. Arguments passed to the message follow a colon.

Image

Figure 3-4 A simple Objective-C message

You can, of course, have multiple-argument methods, as you will see in the next chapter. For now, though, just consider single-argument methods. Figure 3-4 summarizes an Objective-C message with a single argument.

Using self in a Message

The term self refers to an object when sending a message, and it is also the receiver. For instance, you might make a mental note to yourself to pick up milk on the way home from work (Listing 3-8).

Listing 3-8 A method using the self keyword

-(void) goHome {
  Milk * myMilk = [self pickupMilk];
}
-(Milk*) pickupMilk {
  // pick up milk logic
}

Both methods are in the same object, and so the goHome method sends the message pickupMilk to itself, or self.

Nested Arguments

As when programming in Java, you can nest Objective-C messages. For instance, using Java, you might write the following:

objMyObject.getFoo(objMyFooIdentifier.getID());

In Objective-C, you would write the same statement as follows:

[objMyObject getFoo: [objMyFooIdentifier getID]];

Using Java, you might nest an object’s constructor in another method.

objTester.testFubar(new Fubar(33));

In Objective-C, you can also nest object constructors in other methods.

[objTester testFubar[[Fubar alloc] initWithInteger : 33]]];

In this method, a new Fubar instance is first allocated and then initialized with 33, and the resulting object reference is sent as an argument to the testFubar message.

Class and Instance Methods

As discussed earlier, you declare methods in a class’ interface and define methods in a class’ implementation. Just as in C, a method declaration consists solely of the method’s signature, while the definition is the method’s actual implementation. In both files, there are two method types: instance and class methods. Instance methods begin with a minus sign, while class methods begin with a plus sign. A class method is similar to a Java static method, meaning you don’t need to create a class instance to use the method. For instance, the following is an instance method.

-(void) sayHello: (NSString*) name

Using the method requires creating a class instance first. Although not required, you should also initialize the class. Remember, all classes extend NSObject, which has an init method, so every Objective-C class is guaranteed to implement init.

Simple *objSimple = [[Simple alloc] init];
[objSimple sayHello:@"James"];

Now consider class methods. Class methods begin with a plus sign.

+ (void) sayGoodBye;

A class method doesn’t require creating a class instance before using the method. For instance, when first allocating space for an object instance, you call a class’ alloc method. If the class doesn’t implement the alloc method, the runtime traverses up the class’ inheritance hierarchy until it finds an alloc method or it reaches NSObject’s alloc method and calls it.

Simple *mySimple = [Simple alloc];
[mySimple init];

The alloc method is a class method example. You don’t instantiate a class instance before calling alloc; rather, you call alloc directly using the class. You create and use class methods just like Java static methods. And Objective-C class methods have the same restrictions as Java static methods. You can’t reference that class’ instance variables from a static method, as the instance variables haven’t been initialized. You also can’t refer to other instance methods from the same class as the class method. Remember, as with a Java static method, you are using an uninitialized class, not an initialized object. If your class method relies upon a class being initialized, runtime errors will result.

Try This: Adding sayGoodBye as a Class Method

1. Open the last example’s project in Xcode. Open Simple.h and add the sayGoodBye method declaration to it (Listing 3-9). Be certain to use a + and not a – in the method’s signature.

Listing 3-9 Simple.h modified to include sayGoodBye declaration

#import <Foundation/Foundation.h>
@interface Simple : NSObject {
}
+ (void) sayGoodBye;
-(void) sayHello: (NSString *) name;
@end

2. Add the method’s definition to Simple.m (Listing 3-10).

Listing 3-10 Simple.m modified to include sayGoodBye definition

#import "Simple.h"
@implementation Simple
+ (void) sayGoodBye {
  NSLog(@"Goodbye...");
}
-(void) sayHello: (NSString *) name {
  NSMutableString *message =
    [[NSMutableString alloc]initWithString:@"Hello there"];
  [message appendString:name];
  NSLog(message);
  [message release];
}
@end

3. Have main.m call the sayGoodBye method as in Listing 3-11.

Listing 3-11 The main.h file modified to call sayGoodBye

int main(int argc, char *argv[]) {
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  Simple * mySimple = [[Simple alloc] init];
  [mySimple sayHello:@"James"];
  [mySimple release];
  [Simple sayGoodBye];
  int retVal = UIApplicationMain(argc, argv, nil, nil);
  [pool release];
  return retVal;
}

4. Build and run the application, and “Goodbye...” will be written to the debugger console (Listing 3-12).

Listing 3-12 Debugger console after running ChapThree

[Session started at 2010-12-17 21:33:38 -0500.]
2010-12-17 21:33:40.498 ChapThree[851:20b] Hello there James
2010-12-17 21:33:40.501 ChapThree[851:20b] Goodbye...

The alloc and init Methods

The alloc method is how you create class instances for all Objective-C classes. This method allocates memory space for the new object instance. It is inherited from the NSObject class, so you don’t really need to implement this method yourself.

The init method is how you initialize a class once allocated. Unlike the class method alloc, the init method is an instance method. The init method is also a method in NSObject. If a class has no specific initialization requirements, you don’t need to override init, nor are you required to call it when instantiating a class. However, if you have specific initialization requirements, you should override this method. Good programming practice, though, is to always call init, usually on the same line as the allocation.

The init method returns an id. An id is an Objective-C type that is a pointer to the object instance’s address. The id is weakly typed, though, and the runtime treats all ids the same. Overriding an init method should always call the class parent’s init method (Listing 3-13).

Listing 3-13 A simple init implementation

-(id) init {
  if (self = [super init]){
    magicNumber = 5;
  }
  return self;
}

In Listing 3-13, the method assigns itself to its parent’s id. If the parent’s init method fails, it returns a nil value and the if statement fails. If it succeeds, the evaluation is true and the instance variable, magicNumber, is set to five. The init method ends by returning itself.

You can also initialize an object by passing arguments. By convention, initialization methods that take arguments are named init, followed by the data type of the argument. For instance, you could modify the init method in Listing 3-13 to Listing 3-14 if you wanted to initialize with an integer passed as a parameter.

Listing 3-14 A simple init method

-(id) initWithInt : (int) value {
  if (self = [super init]) {
    magicNumber = value;
  }
  return self;
}

Managing Memory Using Retain and Release

Unlike in Java or C#, when programming for iOS, you manage memory manually; there is no garbage collection on iOS devices. Although as of OS X 10.5, Cocoa includes an option to use automatic garbage collection, this option is not available on iOS devices. Table 3-1 summarizes Objective-C’s memory management methods.

Objective-C uses reference counts to determine if memory should be released or retained. When you create a class instance, the runtime allocates memory for the object and assigns that object a reference count of one. For instance, suppose you had a class named Simple. You first allocate space for it using NSObject’s alloc method.

Simple *objSimple = [[Simple alloc] init];

You then use the object.

[objSimple sayHello:@"James"];

When finished, you call its release method. If no release method is found, the runtime moves up the classes’ inheritance hierarchy until it finds a release implementation.

Image

Table 3-1 NSObject Memory Management–Related Methods

As all classes extend NSObject, if no release instance is found, the runtime calls NSObject’s release method.

[objSimple release];

When an object’s reference count reaches zero, the runtime calls the object’s dealloc method to deallocate the object. As with release, if the runtime doesn’t find a dealloc method, it moves up the inheritance hierarchy until it finds one. If no dealloc method is found, the runtime calls NSObject’s dealloc method and the object is deallocated so that the memory can be reclaimed.

You’ve already seen how alloc, release, and dealloc work; you allocate memory for an object and assign it a reference count of one using the alloc method, and you decrement the reference count by one when calling release. When an object’s reference count reaches zero, the program calls NSObject’s dealloc method.

The retain method increments an object’s reference by one and returns the object reference as an id. Unlike Java, this referencing isn’t automatic; you must explicitly call retain to increase an object’s reference count. For instance, consider the following Objective-C code (Listing 3-15).

Listing 3-15 Using retain

Simple *objSimple = [[Simple alloc] init];
Simple *objSimpleTwo = objSimple;
NSLog(@"retaincount: %d", [objSimple retainCount]);
[objSimple release];
//the next line causes an error because objSimpleTwo is released
[objSimpleTwo sayHello:@"James"];

NOTE
Typically, there is no reason to call retainCount. In this chapter, I use retainCount to illustrate Objective-C memory management.

The first line allocates objSimple, and the runtime assigns the object a reference count of one. The second statement creates a new pointer to the objSimple object; both objSimple and objSimpleTwo point to the same physical object in memory. But because the code doesn’t call retain, the physical object’s reference count is not incremented. When the object is then released, the reference count is decremented by one and the reference count for the object becomes zero. The object is deallocated, so the next line fails, as objSimpleTwo is pointing to deallocated memory space.

Instead, the code should have explicitly retained objSimpleTwo.

[objSimpleTwo retain];

Retaining objSimpleTwo would have incremented the object’s reference count by one, bringing it to two. Then, when objSimple was released, the object’s reference count would still be one and the object would not be deallocated. The subsequent call to sayHello would work just fine, as the object that objSimpleTwo pointed to would still exist. Note, this is a somewhat unrealistic example, as you will never write code like Listing 3-15, but it illustrates retain and release.

You can override the NSObject’s retain, release, dealloc, and alloc methods. But if you do, be certain to call the object’s super method version. The method call for these methods must make it up the inheritance hierarchy to NSObject for memory management to function correctly.

Try This: Using Manual Memory Management

1. Open the previous Try This project and implement dealloc, retain, release, and alloc in Simple.m (Listing 3-16). Note that retain returns an id, and that all these methods are declared in NSObject and don’t require you to add their signatures to Simple.h.

Listing 3-16 Simple.m modified to include memory management methods

#import "Simple.h" @implementation Simple
+ (void) sayGoodBye {
  NSLog(@"Goodbye...");
}
-(void) sayHello: (NSString *) name {
  NSMutableString *message = [[NSMutableString alloc]
        initWithString:@"Hello there"];
  [message appendString:name];
  NSLog(message); [message release];
}
-(void) dealloc {
  NSLog(@"deallocating Simple....");
  [super dealloc];
}

-(id) retain {
  NSLog(@"retaining Simple..... ");
  return [super retain];
}
-(void) release {
  NSLog(@"releasing Simple..... ");
  [super release];
}
+(id) alloc {
  NSLog(@"allocating Simple.... ");
  return [super alloc];
}
@end

2. Modify main.m to write log statements of the Simple’s retainCount (Listing 3-17).

Listing 3-17 The main.h file modified to include retainCount logging

#import <UIKit/UIKit.h>
#import "Simple.h"
int main(int argc, char *argv[]) {
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Simple * mySimple = [[Simple alloc] init];
  NSLog(@"retainCount: %d", [mySimple retainCount]);
  [mySimple sayHello:@"James"];
  [mySimple release];
  [Simple sayGoodBye];
  int retVal = UIApplicationMain(argc, argv, nil, nil);
  [pool release];
  return retVal;
}

3. Build and run the application. The debugger includes the logging added in Listing 3-16 (Listing 3-18).

Listing 3-18 Debugger console echoing memory management logging

[Session started at 2010-12-17 22:30:02 -0500.] 2010-12-17 22:30:03.894
ChapThree[1062:20b] allocating Simple....  2010-12-17 22:30:03.895
ChapThree[1062:20b] retaincount: 1 2010-12-17 22:30:03.899
ChapThree[1062:20b] Hello there James 2010-12-17 22:30:03.903
ChapThree[1062:20b] releasing Simple.....  2010-12-17 22:30:03.904
ChapThree[1062:20b] deallocating Simple.... 2010-12-17 22:30:03.904
ChapThree[1062:20b] Goodbye...

In main.m, the main method first allocates a new Simple instance and assigns the pointer (mySimple) to point to the newly allocated and initialized object.

Simple *mySimple = [[Simple alloc] init];

The reference count to the object mySimple points to is one, and the debug statement in Listing 3-17 prints a retainCount of one.

Instance Variables and Memory

In Chapter 4, you will learn about properties. You should use them and their accessor methods. If you do, you avoid this section’s complications. But you should still understand a little about instance variables and how they are handled in memory. Suppose you have an instance variable, personName, you wish to set, as in Listing 3-19.

Listing 3-19 An instance variable in Simple.h

#import <Foundation/Foundation.h>
@interface Simple : NSObject {
  NSString * personName;
}
-(void) sayGoodBye;
-(void) sayName;
-(void) sayHello: (NSString *) name;
@end

Now suppose you modified sayHello to set personName, as in Listing 3-20. You must retain the variable; otherwise, when the caller of sayHello releases the string, it will go away and the personName instance variable will be pointing to unallocated memory.

Listing 3-20 Retaining an instance variable

-(void) sayHello: (NSString*) name {
  NSMutableString *message = [[NSMutableString alloc]
        initWithString:@"Hello there "];
  [message appendString:name];
  NSLog(message);
  personName = [name retain];
  [message release];
}

Note that by retaining name, you are increasing its reference count by one, returning it, and then setting personName to it. This ensures that the string pointed to by personName will not be deallocated until Simple is finished with it. Not retaining a variable when assigning it to another variable, as in Listing 3-20, is a good example of the type of problem you might encounter when not using properties. When the name variable pointer is passed to sayHello, assume there is only one other pointer pointing to name (a retainCount of one). Then, after assigning personName to name, the retainCount remains one. The personName pointer is now at the mercy of the pointer that originally pointed to name outside the sayHello method. When the pointer external to Simple releases the object name points to, the object is deallocated. So the personName pointer now points to deallocated memory space and an error occurs. To correct this problem, you call retain on the instance variable as in Listing 3-20. Anytime you set an instance variable, you should retain it. That way, you ensure that it will not reach a zero reference count while the instance variable still points to the object. Of course, the better solution is to always use accessor methods combined with properties. You learn about accessor methods and properties in the next chapter.

NOTE
You could have written the code in Listing 3-20 using one of NSString’s class methods. But using stringWithString would not illustrate using retain.

personName = [NSString stringWithString:name];

Managing Memory Using Autorelease

Managing reference counts manually is tiresome and error-prone. NSObject’s autorelease method manages an object’s reference count for you. The autorelease method uses what’s called a release pool to manage an object’s references. Refer to Listing 3-17 and note that this method’s first step is allocating an NSAutoreleasePool. Its second-to-last step is releasing that pool. Calling autorelease adds the object to the pool, and the pool retains the object for you. Consider the sayHelloTom method in Listing 3-21.

Listing 3-21 A method using autorelease

-(void) sayHelloTom {
  Simple *objSimple = [[[Simple alloc] init] autorelease];
  [objSimple sayHello:@"Tom"];
}

The method allocates a Simple instance and then calls autorelease, assigning objSimple to the autorelease pool. When the method finishes executing, the autorelease pool is deallocated and the Simple instance is subsequently released.

NOTE
The iOS operating system creates an autorelease pool for every event loop and releases it when the loop completes.

Using autorelease and accepting the default autorelease pools makes memory management easy. However, the problem is that there’s a penalty: The objects persist for the release pool’s lifetime. There is one solution, and that is to manage the NSAutoReleasePool yourself. For instance, you could modify Listing 3-22 to manage its own autorelease pool.

Listing 3-22 The sayHelloTom method managing its own autorelease pool

-(void) sayHelloTom {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  Simple *objSimple = [[[Simple alloc] autorelease] init];
  [objSimple sayHello:@"Tom"];
  [pool release];
}

When the pool is released and deallocated, the pool releases the object pointed to by objSimple. When the physical object pointed to by objSimple is released, the reference count is zero, so the runtime deallocates the object. You should note, though, that in this example, the results are exactly the same as if you had used the default autorelease pool. Unless creating many objects, it’s probably best to stick to the default NSAutoreleasePool rather than trying to manage it yourself. However, given the constrained resources of many iOS devices, the preferred way to manage memory is to do it manually.

Summary

You’re not finished with Objective-C yet. We still haven’t learned about properties, multipleargument messages, Objective-C’s dynamic binding and typing, inheritance, composition, categories, protocols, or handling exceptions. While this might seem like a lot that you still need to learn, Objective-C is a full-featured object-oriented language and you will eventually want to take advantage of all of its capabilities while developing for iOS. Despite the length of this and the next chapter, realize you are only scratching the surface of Objective-C.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset