Coding in Objective-C - Learning iPhone Programming (2010)

Learning iPhone Programming (2010)

Chapter 4. Coding in Objective-C

Thus far, you’ve built a simple iPhone application and discovered that it’s not that hard to build apps for the iPhone or iPod touch. Let’s step back and take a broader look at the Objective-C language.

Objective-C is an object-oriented language that adds Smalltalk-style messaging to C. The language is a superset of the C language, providing constructs to allow you to define classes and objects. Once you get the hang of the Smalltalk-style syntax, if you’ve programmed in an object-oriented language before, things should look fairly familiar. However, there are some differences, and I discuss them in this chapter. One of the bigger differences, especially for those who are coming from a Java background, is in how Objective-C deals with memory management.

Declaring and Defining Classes

As is the case in almost all other object-oriented languages, in Objective-C classes provide the building blocks to allow encapsulation of data and methods that act on that data. Objects are specific instances of a class, and they contain their own instance data and pointers to the methods implemented by the class. Classes are specified in two pieces: the interface and the implementation. The interface contains the declaration of the class and is normally contained in a .h file. The implementation contains your actual code (the definition) and is normally contained in a .m file. We briefly discussed this in Chapter 3, but let’s take some time to look at it in more detail here.

Declaring a Class with the Interface

Let’s return to the declaration of the HelloWorldViewController class from Chapter 3, which illustrates a typical class interface. The interface begins with the @interface keyword, followed by the name of the class being declared and ending with a colon followed by the name of the base (or parent) class:

@interface HelloWorldViewController : UIViewController

An Objective-C class cannot inherit from multiple classes; however, the class it inherits from may in turn inherit from another class. In the case of HelloWorldViewController, its base class is UIViewController, which itself inherits from UIResponder, which inherits fromNSObject, the root class of most Objective-C class hierarchies.

Warning

Objective-C allows objects to descend from any root class. Although NSObject is the most common root class, it is not the only one. For instance, NSProxy is also a root class. So, you cannot always assume that a given class is derived from NSObject.

After that first line, the instance variable declarations appear within curly braces. Following that, we have the declaration of properties and methods associated with the class. The class declaration is wrapped up with the @end keyword:

#import <UIKit/UIKit.h>1

@interface HelloWorldViewController : UIViewController {

UIButton *button;

UILabel *label;

}

@property (nonatomic, retain) IBOutlet UILabel *label;

-(IBAction)sayHello:(id) sender;

@end

1

The #import statement is not technically part of the class declaration. Instead, this is a C preprocessor directive that avoids multiple inclusions of the same header file and is effectively equivalent to the C preprocessor directive #include <UIKit/UIKit.h>.

Defining a Class with the Implementation

The HelloWorldViewController implementation from Chapter 3 begins by importing the class interface in the .h file. The implementation begins with the @implementation declaration and ends with the @end declaration:

@implementation HelloWorldViewController

...

@end

After the implementation begins, we must synthesize the accessor for the properties we declared in our interface file and implement the declared methods:

#import "HelloWorldViewController.h"

@implementation HelloWorldViewController

@synthesize label;

-(IBAction) sayHello:(id) sender {

label.text = @"Hello World";

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

}

- (void)viewDidUnload {

}

- (void)dealloc {

[label release];

[button release]

[super dealloc];

}

@end

Now that you’ve taken a quick look at the structure of an interface and implementation, let’s take a detailed look at the individual parts.

Object Typing

When instance variables are themselves objects—for instance, when the HelloWorldViewController class declares UIButton and UILabel variables—you should always use a pointer type. However, Objective-C adds an interesting twist: it supports both strongly typed and weakly typed declarations. Here’s a strongly typed declaration:

UIButton *button;

Here we declare anObject. In the first instance we use strong typing, declaring it as an object of the class SomeClass.

Here’s a weakly typed version of the declaration, where it is declared as an object of class id:

id button;

The id class is a generic C type that Objective-C uses to represent an arbitrary object; it’s a general type representing any type of object regardless of class and can be used as a placeholder for both a class and a reference to an object instance. All objects therefore are of type id. This can prove very useful; you can imagine that if you wanted to build a generic class implementing a linked list, the type of object held in each node would be of type id, since you’d then be able to store any type of object.

Properties

The declaration of properties using the @property compiler directive is a convenience to avoid the declaration and, usually, the implementation of accessor methods for member variables. You can think of a property declaration as the equivalent of declaring accessor methods. You can also dictate how the automatically generated accessor methods behave by declaring custom attributes (see the sidebar ). In the HelloWorldViewController class, we declare the property to be (nonatomic, retain):

@property (nonatomic, retain) IBOutlet UILabel *label;

We can also declare both of our properties to be an IBOutlet. While not formally part of the list of attributes for an @property declaration, IBOutlet denotes that this property is an Interface Builder outlet. I talked about outlets briefly in Chapter 3 and will discuss them in more detail later.

Declaring Custom Attributes for Properties

Accessor Methods

By default, the automatically generated accessor methods created when you @synthesize a property are propertyName: and setPropertyName:. You can change this by using the getter=getterName and setter=setterName custom attributes. Bear in mind that changing the default names will invariably break the dot syntax syntactic sugar (see The Dot Syntax) that Objective-C normally provides.

Writability

You can choose whether the property has an associated setter accessor method by specifying the readonly custom attribute. If this is set, only a getter method is generated when you @synthesize the property in your implementation.

Setter Semantics

The assign, retain, and copy custom attributes govern the setter accessor method and are mutually exclusive. The assign attribute is the default and implies that the generated setter uses simple assignment. The retain attribute specifies that a retain should be invoked on the object when it is assigned, and the previous value should be sent a release message. See Memory Management for the implications of this constraint. Finally, the copy attribute implies that a copy of the object should be used when the object is assigned, rather than a straight assignment. This attribute is valid only for objects that implement the NSCopying protocol.

Atomicity

The nonatomic custom attribute specifies that the accessor method is nonatomic. Properties are atomic by default so that the accessor methods are robust in multithreaded environments. Note that while the accessor is robust, it is not necessarily thread-safe. Specifying nonatomicimplies that the accessor is conversely not robust in such environments, and that the generated accessor method returns the object directly. However, it does result in considerably faster code and is generally recommended for iPhone applications.

Synthesizing Properties

When you declare an @property in the class interface, you must also synthesize the property (unless you wish to implement the getter and setter methods yourself) using the @synthesize declaration, as we do for the label property in the HelloWorldViewController class:

@synthesize label;

This asks the compiler to generate the accessor methods according to the specification in the property declaration, and much reduces the amount of boilerplate code that you have to write yourself.

The Dot Syntax

When you declare a member variable as a property and synthesize the declared accessors using the @synthesize declaration in the @implementation of the class, you can (entirely optionally) make use of some syntactic sugar that Objective-C provides, called the dot syntax, as an alternative to using the automatically generated accessor methods directly. For instance, this lets us do the following:

label.text = @"Hello World";

instead of doing this (note that Objective-C capitalized the t in text when it generated the accessor method):

[label setText:@"Hello World"];

The dot syntax is arguably somewhat neater and easier to read.

Declaring Methods

We declare one method in the HelloWorldViewController class, called sayHello:.

#import <UIKit/UIKit.h>

@interface HelloWorldViewController : UIViewController {

UILabel *label;

UIButton *button;

}

@property (nonatomic, retain) IBOutlet UILabel *label;

-(IBAction)sayHello:(id) sender;

@end

The minus sign in front of the method indicates the method type, in this case an instance method. A plus sign would indicate a class method. For example:

+(void)aMethod:(id) anObject;

The sayHello: method takes an id object as an argument and is flagged as an IBAction for Interface Builder. When compiled, IBAction is replaced with void and IBOutlet is removed; these compiler directives are simply used to flag methods and variables to Interface Builder. This method is passed a generic id object as an argument since we intended it to be triggered by a UI event, and we want to leave it open as to what sort of UI element will be used. Under our UI, it’s triggered when the user clicks the “Push me!” button in the UI, and this id object will be theUIButton that the user clicked to trigger the HelloWorld event application. We can recover the UIButton object by casting the sender object to a UIButton:

UIButton * theButton = (UIButton *)sender;

It’s a standard practice in Objective-C to call such objects sender. If we were unsure of the underlying type of an id object, we could check the type using the isKindOfClass method:

if([thisObject isKindOfClass:[anotherObject class]]) { ... }

Calling Methods

If you want to call a method exposed by an object, you do so by sending that object a message. The message consists of the method signature, along with the parameter information. Messages are enclosed in square brackets; the object receiving the message is on the left and the parameters are on the right, with the parameter following a colon. If the method accepts more than one argument, this is explicitly named, and the second parameter follows a second colon. This allows multiple methods with the same name and argument types to be defined.

[anObject someMethod];

[anObject someMethod: anotherObject];

[anObject someMethod: anotherObject withAnotherArgument: yetAnotherObject];

The name of the method is the concatenation of the method name and any additional named arguments. Hence in the preceding code we have someMethod: and someMethod:withAnotherArgument:. This may seem odd to people coming in from other languages, which usually have much terser naming conventions, but in general Objective-C method names are substantially more self-documenting than in other languages. Method names contain prepositions and are made to read like sentences. The language also has a fairly entrenched naming convention, which means that method names are fairly regular.

Note

While Objective-C method names are long, Xcode will perform code completion as you type. Press Return to accept its suggestion, or F5 to present a pop-up list of matching methods. Pressing Ctrl-/ will step you through the parameters of the method.

Methods can return output, as shown here:

output = [anObject someMethodWithOutput: anotherObject];

And they can be nested, as in the following:

output = [anObject someMethodWithOutput: [anotherObject someOtherMethod]];

When I originally started writing in Objective-C, one of the main problems I had with the language was the way it dealt with method calls. For those of us who are coming from more utilitarian languages, the behavior of Objective-C in this regard does seem rather strange. Although Objective-C code can be valid and not follow the rules I’ve described here, modern Objective-C is not really separable from the Cocoa framework, and Cocoa rules and conventions have become Objective-C’s rules and conventions.

Calling Methods on nil

In Objective-C, the nil object is functionally equivalent to the NULL pointer found in many other C-derived languages. However, unlike most of these languages, it is permissible to call methods on nil without causing your application to crash. If you call a method on (although in Objective-C we are actually passing a message to) the nil object type, you will get nil returned.

Memory Management

The way memory is managed in Objective-C on the iPhone is probably not what you’re used to if you’re coming in from a language such as Java. If you’re writing an application in Objective-C for the Mac, you have the option of enabling garbage collection; however, on the iPhone you are restricted to using reference counting. This isn’t as bad as it seems, and sticking to a few simple rules means that you can manage the memory that is allocated.

Creating Objects

You can create an object in two ways. As shown in the following code, you can manually allocate the memory for the object with alloc and initialize it using init or an appropriate initWith method (e.g., NSString has an initWithString method):

NSString *string = [[NSString alloc] init];

NSString *string = [[NSString alloc] initWithString:@"This is a string"];

Alternatively, you can use a convenience constructor method. For instance, the NSString class has a stringWithString class method that returns an NSString object:

NSString *string = [NSString stringWithString:@"This is a string"];

In the preceding two cases, you are responsible for releasing the memory you allocated with alloc. If you create an object with alloc, you need to release it later. However, in the second case, the object will be autoreleased. You should never manually release an autoreleased object, as this will cause your application to crash. An autoreleased object will, in most cases, be released at the end of the current function unless it has been explicitly retained.

The Autorelease Pool

The autorelease pool is a convenience that defers sending an explicit release message to an object until “later,” with the responsibility of freeing the memory allocated to objects added to an autorelease pool devolved onto the Cocoa framework. All iPhone applications require a default autorelease pool, and the Xcode template inside the main.m file creates it for us:

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

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

int retVal = UIApplicationMain(argc, argv, nil, nil);

[pool release];2

return retVal;

}

1

The default autorelease pool is set up prior to entering the main event loop.

2

The default autorelease pool is drained after exiting the loop.

An additional inner autorelease pool is created at the beginning of each event cycle (i.e., iteration through your application’s event loop), and is released at the end.

The need for and existence of autorelease makes more sense once you appreciate why it was invented, which is to transfer control of the object life cycle from one owning object to another without immediately deallocating the object.

The alloc, retain, copy, and release Cycle

Although the autorelease pool is handy, you should be careful when using it because you unnecessarily extend the time over which the object is instantiated, thereby growing your application’s memory footprint. Sometimes it makes a lot of sense to use autoreleased objects. However, beginning Cocoa programmers often overuse convenience constructors and autoreleased objects.

Note

Apple, writing in its Cocoa Fundamentals guide, officially discourages the use of autorelease objects on the iPhone due to the memory-constrained environment on the device, stating that “Because on iPhone OS an application executes in a more memory-constrained environment, the use of autorelease pools is discouraged in methods or blocks of code (for example, loops) where an application creates many objects. Instead, you should explicitly release objects whenever possible.”

When handling memory management manually using the retain count and the alloc, retain, and release cycle (see Figure 4-1), you should not release objects you do not own. You should always make sure your calls to retain are balanced by your calls to release. You own objects that you have explicitly created using alloc or copy, or that you have added to the retain count of the object using retain. However, you do not own objects you have created using convenience constructors such as stringWithString.

The alloc-retain-release cycle; an object is allocated, retained, and then released twice, bringing the reference count back to zero and freeing the memory

Figure 4-1. The alloc-retain-release cycle; an object is allocated, retained, and then released twice, bringing the reference count back to zero and freeing the memory

When releasing the object, you have the option of sending it either a release message or an autorelease message:

[anObject release];

[anObject autorelease];

Sending a release message will immediately free the memory the object uses if that release takes the object’s retain count to zero, while sending an autorelease message adds the object to the local autorelease pool. The object will be released when the pool is destroyed, normally at the end of the current function.

If your object is a delegate of another object, you need to set the delegate property of that object to nil before you release your original object.

The dealloc Method

The dealloc method is called when an object is released. You should never call this method directly, but instead send a release message to the object, because the object may contain references to other objects that will not be deallocated.

As we did in the HelloWorldViewController class, you should always override the dealloc method in your own objects and in release objects you have created or retained:

- (void)dealloc {

[label release];

[button release]

[super dealloc];

}

In this method, we released the label and button instance variables. We then called the dealloc method of the superclass. It is entirely permissible to send a release message to a nil object.

Responding to Memory Warnings

Your code must respond to memory warnings. Let’s look at the HelloWorldViewController implementation from Chapter 3 again. It implements the didReceiveMemoryWarning method:

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

}

This is where you should release any large blocks of memory—for instance, image or web caches—that you are using. If you ignore a memory warning, your application may crash. The iPhone does not have any sort of virtual memory or swap file; when the device runs out of memory there really is no more memory to allocate. It’s possible, and advisable, to test your application by simulating a memory warning in iPhone Simulator, which you can do by selecting Hardware→Simulate Memory.

Fundamental iPhone Design Patterns

When you write code you’re probably already using patterns, although possibly you’re doing so without realizing it. A design pattern is just a reusable solution, a template, for how to approach commonly occurring problems. A pattern is not code, but instead describes how you should model the application in terms of the classes that are used, and how they should structure the interactions and relationships between these classes.

The Cocoa Touch framework underlying your iPhone applications is based on one of the oldest design patterns, the Model-View-Controller (MVC) pattern, which dates from the 1970s. The MVC pattern is used to separate the program logic from the UI, and is the generally accepted way to build iPhone applications. As it is used so extensively inside Apple’s own frameworks, including the UIKit framework, it would be quite hard to write an iPhone application without using this pattern in your implementation. While you could write an iPhone application without referencing the MVC pattern, it is enormously difficult to fight the underlying frameworks; you should instead work with them. Attempting to write iPhone applications while ignoring the underlying MVC patterns is a pointless exercise in make-work.

The Model-View-Controller Pattern

The MVC pattern divides your application into three functional pieces:

Model

The model manages the application state (and associated data) and is usually persistent. It is entirely decoupled from the UI or presentation of the application state to the user.

View

The view is what the user sees, and it displays the model for the user. It allows the user to manipulate it and respond and generate events. In iPhone applications, the view is normally built inside Interface Builder rather than programmatically.

Controller

The controller coordinates updates of the view and the model when user interaction with the view makes changes to the model, and vice versa. This is typically where most of the application logic lives.

We implemented our Hello World application from Chapter 3 using this pattern. We created the view using Interface Builder, and the HelloWorldViewController class managed the view. The application was too simple to require an explicit class to manage the application’s state; effectively, the model was embedded in the ViewController class. If we were strictly adhering to the design pattern, we would have implemented a further class that our sayHello: method would have queried to ask what text should have been displayed.

The model class is usually a subclass of NSObject and has a set of instance variables and associated accessor methods, along with custom methods to associate the internal data model.

Views and View Controllers

I’ve talked about both views and view controllers quite a lot, and while so far we’ve built our views in Interface Builder and then handled them using our own view controller code, that isn’t the only way to build a view. You can create views programmatically—in fact, in the early days of iPhone development you had to do things that way.

However, Interface Builder has made things a lot easier, and I recommend that in most cases you build your views using it if you can. When you used Interface Builder to construct your view you edited a NIB file, an XML serialization of the objects in the view. Using Interface Builder to create these objects, and to define the relationship between them and your own code, saves you from writing large amounts of boilerplate code that you would otherwise need to manage the view.

If you want to create your view manually, you should override the loadView: method of your view controller class, as this is the method the view controller calls when the view property is requested but is currently set to nil. Don’t override this method if you’ve created your view using the initWithNibName: method, or set the nibName or nibBundle properties. If you’re creating your view manually and you do override this method, however, you must assign the root view you create to the view property of your view controller class:

-(void) viewDidLoad {

UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0,0,320,480)];

.

.

.

self.view = view;

[view release];

}

Your implementation of this method should not call [super viewDidLoad], as the default implementation of this method will create a plain UIView if no NIB information is present and will make this the main view.

The Delegates and DataSource Pattern

I talked briefly about delegates in Chapter 3. An object that implements a delegate protocol is one that acts on behalf of another object. To receive notification of an event to which it must respond, the delegate class needs to implement the notification method declared as the delegate protocol associated with that event. The protocol may, and usually does, specify a number of methods that the delegate class must implement.

Data sources are similar to delegates, but instead of delegating control, if an object implements a DataSource protocol it must implement one or more methods to supply data to requesting objects. The delegating object, typically something such as a UITableView, will ask the data source what data it should display; for instance, in the case of a table view, what should be displayed in the next UITableViewCell when it scrolls into the current view.

Declaring that a class is a data source or a delegate flags the object for Interface Builder so that you can connect the relevant UI elements to your code. (We’ll be talking about UITableView in Chapter 5.) To declare that AnObject was both a table view data source and a delegate, we would note this in the @interface declaration:

@interface AnObject: UIViewController <UITableViewDataSource,

UITableViewDelegate> {

...

}

This would mean that the AnObject object, a UIViewController, is responsible for both populating the table view with data and responding to events the table view generates. Another way to say this is that this object implements both the UITableViewDataSource and theUITableViewDelegate protocols.

At this point, you would use Interface Builder, and we’ll be doing that in the next chapter when we build a table-view-based application to connect the UITableView in our view to the data source and delegate object in our code.

Conclusion

This has been a dense chapter and fairly heavy going. However, our discussion of the MVC pattern should show you that this delegation of event handling and of populating data into the UI from the view to a controller class makes sense inside the confines of the pattern, and the availability of these features in Objective-C is one of the reasons why the MVC pattern has been widely adopted.

Note

In this chapter, I was able to give you only a brief overview of Objective-C and the Cocoa Touch framework. Added levels of subtlety are involved in many of the things I covered, but I didn’t have the space to cover them here. My coverage of the basics should give you enough information so that you can pick up the rest as we go along. However, if you intend to develop for the iPhone on a serious level, you should read up on the language in more detail. Apple provides some excellent tutorial material on its Developer website, and that should certainly be your first port of call. However, I also suggest several other books for further reading in Chapter 14.