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> @interface ViewController : UIViewController @property (weak, nonatomic) IBOutlet UILabel *label; - (IBAction)pushButton:(id)sender; @end
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.
In this class, we are relying on the instance variables for the properties to be automatically created when they are synthesized 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.
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
We must ensure that all declared properties have the accessor (getter and setter) methods declared as part of the implementation. In most cases this is done for us by the compiler, vastly reducing the amount of code we need to write ourselves.
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 the property will usually be automatically synthesized by the compiler. This means the accessor methods, along with an underlying instance variable, are automatically generated. However this isn’t always the case and we may sometimes have to manually use the @synthesize
directive to do this task.
Note
You’ll know you need to do this because your code will fail to build with an error that may look something like,
Property 'foo' requires method 'foo' to be defined - use @synthesize, @dynamic or provide a method implementation in this class implementation
However in most cases, at least since the release of Xcode 4.5, you no longer have to worry about this sort of detail.
If you must manually synthesize the property, you do so using the @synthesize
declaration, which should directly follow the @implementation
declaration, e.g.
@synthesize label;
This asks the compiler to generate the accessor methods according to the specification in the property declaration. 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.
Note
You may want to implement the getter and setter methods yourself, and forego using the automatically generated versions. You can go right ahead and do that. If you implement these methods they’ll replace the automatically generated versions.
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
.
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 you’re 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 Hardware→Simulate 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, using Storyboards appears simpler than using nib files, you can quickly get into trouble. While Storyboards 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 ViewController
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 ViewController
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, and that should certainly be your first port of call. However, I also suggest several other books for further reading in Chapter 14.
Get Learning iOS Programming, 3rd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.