O'Reilly logo

Learning iOS Programming, 2nd Edition by Alasdair Allan

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

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 iOS. 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, although this has been massively simplified with the arrival of iOS 5.

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 ViewController 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 ViewController : 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 ViewController, its base class is UIViewController, which itself inherits from UIResponder, which inherits from NSObject, 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, although in the case of the view controller class, we do not declare any and can omit the 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 ViewController : UIViewController

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

- (IBAction)pushButton:(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>.

In this class, we are relying on the instance variables for the properties to be automatically created when we synthesize them inside the implementation of the class. Dynamically creating the instance variables in this manner works on both the modern runtimes (Intel 64-bit and ARM) you are likely to encounter but not on older platforms.

Note

Before the release of iOS 4, the iPhone Simulator was an Intel 32-bit application, and the dynamic addition of properties at runtime was not possible. This is no longer the case.

Note

Dynamically creating instance variables using properties is a feature of Objective-C 2.0, and not a result of preprocessing magic done on your behalf by Xcode.

If instead we wished to manually create the instance variables, we would do as below:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController {
    UILabel *label;
}

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

- (IBAction)pushButton:(id)sender;

@end

which is entirely interchangeable with the initial code.

Defining a Class with the Implementation

The ViewController 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 ViewController

   ...

@end

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

#import "ViewController.h"

@implementation ViewController
@synthesize label;

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)viewDidUnload {
    [self setLabel:nil];
    [super viewDidUnload];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:
      (UIInterfaceOrientation)interfaceOrientation {
          return (interfaceOrientation !=
                  UIInterfaceOrientationPortraitUpsideDown);
}

- (IBAction)pushButton:(id)sender {
    label.text = @"Hello World!";
}

@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 ViewController class declares a UILabel variable—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:

UILabel *label;

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 label;

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 Declaring Custom Attributes for Properties). In the ViewController class, we declare the property to be (weak, nonatomic):

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

We can also declare the property 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.

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. You can use the form:

@synthesize label=_label;

to indicate that a particular instance variable (in this case _label) should be used for the property. This is a useful form to ensure that the values of the property are always accessed using the generated accessor methods and not directly via the instance variable backing the property, which can lead to problems.

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 pushButton:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

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

- (IBAction)pushButton:(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 pushButton: 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 the UI, it’s triggered when the user clicks the Push me! button in the UI, and this id object will be the UIButton that the user clicked to trigger the event. 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 each 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. A pop-up list of suggested methods will appear automatically. You can select a method using the up and down arrow keys, pressing Return to accept a suggestion. Pressing Control-/ 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 and the iPad is probably not what you’re used to if you’re coming in from a language such as Java, Perl, Ruby, or even JavaScript. If you’re writing an application in Objective-C for the Mac, you have the option of enabling garbage collection; however, on the iPhone and iPad, you are restricted to using reference counting. This isn’t as bad as it seems, especially with the arrival of Automatic Reference Counting (ARC), which we will talk about a little later on in the chapter.

Note

Reference counting is a memory management technique where a running count of the number of references to a particular object is kept and used to deallocate the memory when the count reaches zero. See the sectionThe alloc, retain, copy, and release Cycle later in the chapter.

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 iOS 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[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, 
                                 NSStringFromClass([AppDelegate class]));
    }
}

Here, the call to the UIApplicationMain routine is held within the autorelease block, with the default autorelease pool set up prior to entering the main event loop, and then 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 increasing your application’s memory footprint. Sometimes it makes 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 iOS due to the memory-constrained environment on the device, stating that “Because on iOS 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.

Automatic Reference Counting

While it’s vital to understand how the underlying memory management works if your going to be building applications in Objective-C, the arrival of Automatic Reference Counting with the release of iOS 5 has simplified memory management on the iOS platform a great deal.

Instead of you having to remember when to use retain, release, and autorelease, ARC evaluates the requirements of your objects and automatically inserts the appropriate method calls for you at compile time. The compiler also generates appropriate dealloc methods for you.

Note

As iOS 4 is supported as a target for ARC-based applications, non-ARC based code is really only needed when targeting iOS 3.x. Additionally, Apple notes that, “ARC interoperates with manual reference counting code on a per-file basis. If you want to continue using manual reference counting for some files, you can do so.” This is important when making use of legacy libraries that are not ARC ready in ARC-based code. We’ll talk about this later in the book.

It’s really important to realize that ARC is a precompilation step that adds retain, release, and autorelease statements to your code for you, and reference counting has not disappeared, it has simply been automated.

With ARC enabled in your project, the following code:

NSObject *anObject = [[NSObject alloc] init];

// ... code which calls methods on the object

will be transformed during a precompilation step into:

NSObject *anObject = [[NSObject alloc] init];

// ... code which calls methods on the object

[anObject release];

All of the projects we’ll look at in this book use ARC-based templates.

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. Inside an application making use of ARC, there shouldn’t be much reason to override the dealloc method, and of course ARC inserts the release messages for you automatically.

Warning

Even though ARC releases instance variables automatically, there may still be legitimate reasons to override the dealloc method in your own classes. In such cases, and unlike other superclass methods that you may have overridden, you should never call the dealloc method in the superclass directly. ARC will add this for you.

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. Since iOS devices do 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 iOS Simulator, which you can do by selecting HardwareSimulate Memory.

Fundamental iOS 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 iOS 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 iOS applications. As it is used so extensively inside Apple’s own frameworks, including the UIKit framework, it would be quite hard to write an iOS application without using this pattern in your implementation. While you could write an iOS application without referencing the MVC pattern, it is enormously difficult to fight the underlying frameworks; you should instead work with them. Attempting to write iOS 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 iOS 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 the Hello World application from Chapter 3 using this pattern. We created the view using Interface Builder, and the ViewController 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 the pushButton: 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 the views in Interface Builder and then handled them using the 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 iOS 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.

Note

Using Interface Builder and storyboards with iOS 5, it’s now actually possible to build a moderately complicated application with little or no code at all. We're not going to cover Storyboarding in this book. While on the face of things, it appears simpler than using nib files, you can quickly get into trouble. While they have a lot of potential, at least in my opinion, they aren't yet mature technology. At the time of writing, there are some severe issues that make the current implementation burdensome to use in practice.

If you want to create your view manually, you should override the viewDidLoad: 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 the UITableViewDelegate 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 the view to the data source and delegate object in the code.

Conclusion

This has been a dense chapter and fairly heavy going. However, the 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 iOS on a serious level, you should read up on the language in more detail. Apple provides some excellent tutorial material on its Developer website (http://developer.apple.com/iphone/), and that should certainly be your first port of call. However, I also suggest several other books for further reading in Chapter 14.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required