iPhone programming centers on two important
paradigms: objected-oriented programming and the Model-View-Controller
(MVC) design pattern. The iPhone SDK is designed around supporting these
concepts in the programs you build. To do this, it has introduced
delegation (controller) and data source methods (model) and customized
view classes (view).
Object-Oriented Programming
Objective-C is heavily
based on Smalltalk, one of the most historically important
object-oriented languages. Object-oriented programming uses the concepts
of encapsulation and inheritance to build reusable classes with
published external interfaces and private internal implementation. You
build your applications out of concrete classes that can be stacked
together like LEGO toys, because it’s always made clear which pieces fit
together through class declarations.
Pseudo-multiple
inheritance (via invocation forwarding and protocols) provides an
important feature of Objective-C’s approach to object-oriented
programming. iPhone classes can inherit behaviors and data types from
more than one parent. Take the class UITextView, for example. It’s both text and
a view. Like other view classes, it can appear onscreen. It has set
boundaries and a given opacity. At the same time, it inherits
text-specific behavior. You can easily change its display font, color,
or text size. Objective-C and Cocoa Touch combine these behaviors into a
single easy-to-use class.
Model-View-Controller
MVC separates the way
an onscreen object looks from the way it behaves. An onscreen button
(the view) has no intrinsic meaning. It’s just a button that users can
push. That view’s controller acts as an intermediary. It connects user
interactions such as button taps to targeted methods in your
application, which is the model. The application supplies and stores
meaningful data and responds to interactions such as these button taps
by producing some
sort of useful result. MVC is best described in the seminal 1988 paper
by Glenn Krasner and Stephen Pope, which is readily available online.
Each MVC element works
separately. You might swap out a pushbutton with, for example, a toggle
switch without changing your model or controller. The program continues
to work as before, but the GUI now has a different look. Alternatively,
you might leave the interface as is and change your application where a
button triggers a different kind of response in your model. Separating
these elements enables you to build maintainable program components that
can be updated independently.
The MVC paradigm on the iPhone breaks down into the following categories:
Model—
Model methods supply data through protocols such as data sourcing and
meaning by implementing callback methods triggered by the controller.
View— View components are provided by children of the UIView class and assisted by its associated (and somewhat misnamed) UIViewController class.
Controller— The controller behavior is implemented through three key technologies: delegation, target action, and notification.
Together, these three
elements form the backbone of the MVC programming paradigm. Let’s look
at each of these elements of the iPhone MVC design pattern in a bit more
detail. The following sections introduce each element and its
supporting classes.
View Classes
The iPhone builds its views based on two important classes: UIView and UIViewController. These two classes and their descendants are responsible for defining and placing all onscreen elements.
As views draw things on your screen, UIView represents the most abstract view class. Nearly all user interface classes descend from UIView and its parent UIResponder. Views provide all the visual application elements that make up your application. Important UIView classes include UITextView, UIImageViews, UIAlertView, and so forth. The UIWindow class, a kind of UIView, provides a viewport into your application and provides the root for your display.
Because of their
onscreen nature, all views establish a frame of some sort. This frame is
an onscreen rectangle that defines the space each view occupies. The
rectangle is established by the view’s origin and extent.
Views are arranged
hierarchically and are built with trees of subviews. You can display a
view by adding it to your main window or to another view by using the addSubview
method to assign a child to a parent. You can think about views as
attaching bits of transparent film to a screen, each piece of which has
some kind of drawing on it. Views added last are the ones you see right
away. Views added earlier may be obscured by other views sitting on top
of them.
Despite the name, the UIViewController
class does not act as controllers in the MVC sense. They more often act
as view handlers and models than as controllers. Although some will
disagree, Apple terminology does not always match the MVC paradigm
taught in computer science classes.
View
controllers are there to make your life easier. They take
responsibility for rotating the display when a user reorients his or her
iPhone. They resize views to fit within the boundaries when using a
navigation bar or a toolbar. They handle all the interface’s fussy bits
and hide the complexity involved in directly managing interaction
elements. You can design and build iPhone applications without ever
using a UIViewController or one of its
subclasses, but why bother? The class offers so much convenience it’s
hardly worth writing an application without them.
In addition to the base controller’s orientation and view resizing support, two special controllers, the UINavigationController and UITabBarController,
magically handle view shifting for you. The navigation version enables
you to drill down between views, smoothly sliding your display between
one view and the next. Navigation controllers remember which views came
first and provide a full breadcrumb trail of “back” buttons to return to
previous views without any additional programming.
The tabbed view
controller lets you easily switch between view controller instances
using a tabbed display. So if your application has a top ten list, a
game play window, and a help sheet, you can add a three-buttoned tab bar
that instantly switches between these views without any additional
programming to speak of.
Every UIViewController subclass implements a method to load a view, whether through implementing a procedural loadView method or by pulling in an already-built interface from a .xib file and calling viewDidLoad.
This is the method that lays out the controller’s main view. It may
also set up triggers, callbacks, and delegates if these have not already
been set up in Interface Builder.
So in that sense alone, the UIViewController
does act as a controller by providing these links between the way
things look and how interactions are interpreted. And, because you
almost always send the callbacks to the UIViewController
itself, it often acts as your model in addition to its primary role as a
controller for whatever views you create and want to display. It’s not
especially MVC, but it is convenient and easy to program.
Controller
When Apple designs
interactive elements such as sliders and tables, they have no idea how
you’ll use them. The classes are deliberately general. With MVC, there’s
no programmatic meaning associated with row selection or button
presses. It’s up to you as a developer to provide the model that adds
meaning. The iPhone provides several ways in which prebuilt Cocoa Touch
classes can talk to your custom ones. Here are the three most important:
delegation, target-action, and notifications.
Delegation
Many UIKit
classes use delegation to hand off responsibility for responding to user
interactions. When you set an object’s delegate, you tell it to pass
along any interaction messages and let that delegate take responsibility
for them.
A UITableView is a good example of this. When a user taps on a table row, the UITableView
has no built-in way of responding to that tap. The class is general
purpose and it has no semantics associated with a tap. Instead, it
consults its delegate—usually a view controller class or your main
application delegate—and passes along the selection change through a
delegate method. This enables you to add meaning to the tap at a point
of time completely separate from when the table class was first
implemented. Delegation lets classes be created without that meaning
while ensuring that application-specific handlers can be added at a
later time.
The UITableView delegate method tableView: didSelectRowAtIndexPath:
is a typical example. Your model takes control of this method and
implements how it should react to the row change. You might display a
menu or navigate to a subview or place a check mark next to the current
selection. The response depends entirely on how you implement the
delegated selection change method.
To set an object’s delegate, assign its delegate property (this is preferred) or use some variation on the setDelegate:
method. This instructs your application to redirect interaction
callbacks to the delegate. You let Xcode know that your object
implements delegate calls by adding a mention of the delegate protocol
it implements in the class declaration. This appears in angle brackets,
to the right of the class inheritance. Listing 1-1 shows a kind of UIViewController that implements delegate methods for UITableView views. The MergedTableController class is, therefore, responsible for implementing all required table delegate methods.
Xcode’s
documentation exhaustively lists all standard delegate methods, both
required and optional. Open Help > Documentation
(Command-Option-Shift-?) and search for the delegate name, such as UITableViewControllerDelegate. The documentation provides a list of instance methods that your delegate method can or must implement.
Delegation isn’t limited to Apple’s classes. It’s simple to add your own
protocol declarations to your classes and use them to define callback
vocabularies. Listing 1 creates the FTPHostDelegate protocol, which declares the ftpHost
instance variable. When used, that object must implement all three
(required) methods declared in the protocol. Protocols are an exciting
and powerful part of Objective-C programming, letting you create client
classes that are guaranteed to support all the functionality required by
the primary class.
Note
If your application is built around a central table view, use UITableViewController instances to simplify table creation and use.
Listing 1. Defining and Adding Delegate Protocol Declarations to a Class Definition
@protocol FTPHostDelegate <NSObject> - (void) percentDone: (NSString *) percent; - (void) downloadDone: (id) sender; - (void) uploadDone: (id) sender; @end
@interface MergedTableController : UIViewController <UITableViewDelegate,UITableViewDataSource> { UIView *contentView; UITableView *subView; UIButton *button; id <FTPHostDelegate> *ftpHost; SEL finishedAction; } @end
|
Target-Action
Target-actions
are a lower-level way of redirecting user interactions. You encounter
these almost exclusively for children of the UIControl class. With
target-action, you tell the control to contact a given object when a
specific user event takes place. For example, you’d specify which object
to contact when users press a button.
Here is a typical
example. This snippet defines a UIBarButtonItem instance, a typical
buttonlike control used in iPhone toolbars. It sets the item’s target to
self and the action to @selector(trackNotifications:). When tapped, it
triggers a call to the defining object sending the setHelvetica:
message:
UIBarButtonItem *helvItem = [[[UIBarButtonItem alloc]
initWithTitle:@"Helvetica" style:UIBarButtonItemStyleBordered
target:self action:@selector(setHelvetica:)] autorelease];
As you can see, the name of the method (setHelvetica:)
is completely arbitrary. Target-actions do not rely on an established
method vocabulary the way delegates do. In use, however, they work
exactly the same way. The user does something, in this case presses a
button, and the target implements the selector to provide a meaningful
response.
Whichever object defines this UIBarButtonItem instance must implement a setHelvetica:
method. If it does not, the program crashes at runtime with an
undefined method call error. Unlike delegates and their required
protocols, there’s no guarantee that setHelvetica: has been implemented at compile time. It’s up to the programmer to make sure that the callback refers to an existing method.
Standard target-action pairs always pass either zero, one, or two arguments. These arguments are the interaction object and a UIEvent
object that represents the user’s input. Your selector can choose to
pass any or all of these. In this case, the selector uses one argument,
the UIBarButtonItem instance that was
pressed. This self-reference, where the triggered object is included
with the call, enables you to build more general action code. Instead of
building separate methods for setHelvetica:, setGeneva:, and setCourier:, you could create a single setFontFace: method to update a font based on which button the user pressed.
Note
To build target-action into your own UIControl-style classes, add a target variable of type id (any object class) and an action variable of type SEL (method selector). At runtime, use performSelector: withObject: to send the method selector to the object. To use selectors without parameters, for example, @selector(action), pass nil as the object.
Notifications
In
addition to delegates and target-actions, the iPhone uses yet another
way to communicate about user interactions between your model and your
view—and about other events, for that matter. Notifications enable
objects in your application to talk among themselves, as well as to talk
to other applications on your system. By broadcasting information,
notifications enable objects to send state messages: “I’ve changed,”
“I’ve started doing something,” or “I’ve finished.”
Other objects might be
listening to these broadcasts, or they might not. For your objects to
“hear” a notification, they must register with a notification center and
start listening for messages. The iPhone implements many kinds of
notification centers. For App Store development, only NSNotificationCenter is of general use.
The NSNotificationCenter
class is the gold standard for in-application notification. You can
subscribe to any or all notifications with this kind of notification
center and listen as your objects talk to each other. The notifications
are fully implemented and can carry data as well as the notification
name. This name + data implementation offers great flexibility, and you
can use this center to perform complex messaging.
It’s easy to subscribe to a notification center. Register your application delegate or, more typically, your UIViewController as an observer. You supply an arbitrary selector to be called when a notification arrives, in this case trackNotifications:. The method takes one argument, an NSNotification. Ensure that your callback method hears all application notifications by setting the name and object arguments to nil.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(trackNotifications:) name:nil object:nil];
All
notifications contain three data elements: the notification name, an
associated object, and a user information dictionary. If you’re unsure
what notifications UIKitNSLog(@"% @", [notification name]). objects in your application produce, have your callback print out the name from all the notifications it receives—for example,
The kinds of notification vary by the task you are performing. For example, notifications when rotating an application include UIApplicationWillChangeStatusBarOrientation Notification and UIDeviceOrientationDidChangeNotification.
Make sure you implement the trackNotifications:
method (or another callback method whose selector you supplied), which
gets called in this case for all program notifications, regardless of
name or object. Setting these to nil when listening acts as a wild card.
(void) trackNotifications: (NSNotification *) theNotification
{
CFShow([theNotification name]);
CFShow([theNotification object]);
CFShow([[theNotification userInfo] description]);
}
Note
Each debug
feedback method has its advantages and disadvantages. The former have
the advantage of not printing out the date and time, which results in
cleaner output. How you choose to log information is strictly a matter
of taste. There are no wrong or right ways to put print statements into
your program.
Model
You’re responsible for building all application semantics—the model
portion of any MVC app. You create the callback methods triggered by
your application’s controller and provide the required implementation of
any delegate protocol. For relatively simple programs, model details
often are added to a UIViewController subclass. With more complex code, avoid shoehorning that implementation into a UIViewController. Custom-built classes can better help implement semantic details needed to support an application’s model.
There’s one place that the
iPhone SDK gives you a hand with meaning, and that’s with data sources.
Data sources enable you to fill UIKit objects with custom content.
Data Sources
A data source refers to
any object that supplies another object with on-demand data. Some UI
objects are containers without any native content. When you set another
object as its data source, by assigning its dataSource property (preferred) or via a call like [uiobject setDataSource:applicationobject], you enable the UI object (the view) to query the data source (the model) for data such as table cells for a given UITableView.
Usually the data source pulls its data in from a file such as a local
database, from a Web service such as an XML feed, or from other scanned
sources. UITableView and UIPickerView are two of the few Cocoa Touch classes that support or require data sources.
Data sources are like delegates in that you must implement their methods in another object, typically the UITableViewController that owns the table. They differ in that they create/supply objects rather than react to user interactions.
Listing 2
shows a typical data source method that returns a table cell for a
given row. Like other data source methods, it enables you to separate
implementation semantics that fill a given view from the Apple-supplied
functionality that builds the view container.
Objects that implement data source protocols must declare themselves just as they would with delegate protocols. Listing 1 showed a class declaration that supports both delegate and data source protocols for UITableViews.
Apple thoroughly documents data source protocols. You find this
documentation in Xcode’s Documentation window (Help > Documentation).
Listing 2. Data Source Methods Fill Views with Meaningful Content
// Return a cell for the ith row, labeled with its number - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"any-cell"]; if (!cell) { cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"any-cell"] autorelease]; } // Set up the cell cell.text = [tableTitles objectAtIndex:[indexPath row]]; return cell; }
|
The UIApplication Object
In theory, you’d imagine that the iPhone “model” component would center on the UIApplication
class. In practice, it does not, at least not in any MVC sense of the
word model. In the world of the Apple SDK, each program contains
precisely one UIApplication instance, which you can refer to via [UIApplication sharedInstance].
For the most part, unless
you need to open a URL in Safari, recover the key window, or adjust the
look of the status bar, you can completely ignore UIApplication.
Build your program around a custom application delegate class that is
responsible for setting things up when the application launches and
closing things down when the application terminates. Otherwise, hand off
the remaining model duties to methods in your custom UIViewController classes or to custom model classes.
Note
Use [[UIApplication sharedInstance] keyWindow] to locate your application’s main window object.
Uncovering Data Source and Delegate Methods
In addition to
monitoring notifications, message tracking can prove to be an invaluable
tool. Add the following snippet to your class definitions to track all
the optional methods that your class can respond to:
-(BOOL) respondsToSelector:(SEL)aSelector {
printf("SELECTOR: %s\n", [NSStringFromSelector(aSelector)
UTF8String]);
return [super respondsToSelector:aSelector];
}