iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (36 page)

Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online

Authors: Aaron Hillegass,Joe Conway

Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming

BOOK: iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides)
12.56Mb size Format: txt, pdf, ePub
UITableView’s Data Source

The process of providing a
UITableView
with rows in Cocoa Touch is different from the typical procedural programming task. In a procedural design, you tell the table view what it should display. In Cocoa Touch, the table view asks another object – its
dataSource
– what it should display. In our case, the
ItemsViewController
is the data source, so it needs a way to store item data.

 

In
Chapter 2
, you used an
NSMutableArray
to store
BNRItem
instances. You will do the same thing in this chapter, but with a little twist. The
NSMutableArray
that holds the
BNRItem
instances will be abstracted into another object – a
BNRItemStore
(
Figure 9.6
).

 

Figure 9.6  Homepwner object diagram

 

If an object wants to see all of the items, it will ask the
BNRItemStore
for the array that contains them. In future chapters, you’ll make the store responsible for performing operations on the array, like reordering, adding, and removing
BNRItem
s. It will also be responsible for saving and loading the
BNRItem
s from disk.

 
Creating BNRItemStore

From the
File
menu, select
New
and then
New File...
. Create a new
NSObject
subclass and name it
BNRItemStore
.

 

BNRItemStore
will be a singleton. This means there will only be one instance of this type in the application; if you try to create another instance, the class will quietly return the existing instance instead. A singleton is useful when you have an object that many objects will talk to. Those objects can ask the singleton class for its one instance, which is better than passing that instance as an argument to every method that will use it.

 

To get the (single instance of)
BNRItemStore
, you will send the
BNRItemStore
class the message
sharedStore
. Declare this class method in
BNRItemStore.h
.

 
#import
@interface BNRItemStore : NSObject
{
}
// Notice that this is a class method and prefixed with a + instead of a -
+ (BNRItemStore *)sharedStore;
@end
 

When this message is sent to the
BNRItemStore
class, the class will check to see if the single instance of
BNRItemStore
has already been created. If it has, the class will return the instance. If not, it will create the instance and return it. In
BNRItemStore.m
, implement
sharedStore
.

 
+ (BNRItemStore *)sharedStore
{
    static BNRItemStore *sharedStore = nil;
    if (!sharedStore)
        sharedStore = [[super allocWithZone:nil] init];
    return sharedStore;
}
 

Notice that the variable
sharedStore
is declared as
static
. Unlike a local variable, a
static variable
does not live on the stack and is not destroyed when the method returns. Instead, a static variable is only declared once (when the application is loaded into memory), and it is never destroyed. A
static variable
is like a local variable in that you can only access this variable in the method in which it is declared. Therefore, no other object or method can use the
BNRItemStore
pointed to by this variable except via the
sharedStore
method.

 

The initial value of
sharedStore
is
nil
. The first time this method runs, an instance of
BNRItemStore
will be created, and
sharedStore
will be set to point to it. In subsequent calls to this method,
sharedStore
will still point at that instance of
BNRItemStore
. This variable has a strong reference to the
BNRItemStore
and, since this variable will never be destroyed, the object it points to will never be destroyed either.

 

To enforce the singleton status of
BNRItemStore
, you must ensure that another instance of
BNRItemStore
cannot be allocated. One approach would be to override
alloc
in
BNRItemStore
so that it does not create a new instance but returns the existing instance instead.

 

However, there is a problem with this approach:
alloc
is a dummy method. It just calls
allocWithZone:
, which then calls the C function
NSAllocateObject
, which does the actual memory allocation (
Figure 9.7
).

 

Figure 9.7  Default allocation chain

 

Thus, a knowledgeable programmer could still create an instance of
BNRItemStore
via
allocWithZone:
, which would bypass our sneaky
alloc
trap. To prevent this possibility, override
allocWithZone:
in
BNRItemStore.m
to return the single
BNRItemStore
instance.

 
+ (id)allocWithZone:(NSZone *)zone
{
    return [self sharedStore];
}
 

Now if
sharedStore
were to send
alloc
or
allocWithZone:
to
BNRItemStore
, then the method would call
BNRItemStore
’s implementation of
allocWithZone:
. That implementation just calls
sharedStore
, which would then call
BNRItemStore
’s
allocWithZone:
again, which would then call
sharedStore
, which would... well, you get the picture.

 

Figure 9.8  Not sending allocWithZone: to NSObject causes loop

 

This is why we had
sharedStore
call
NSObject
’s implementation of
allocWithZone:
.

 
sharedStore = [[super allocWithZone:nil] init];

By sending
allocWithZone:
to
super
, we skip over our trap and get an instance of
BNRItemStore
when we need it (
Figure 9.9
).

 

Figure 9.9  BNRItemStore and NSObject allocation methods

 

We can only skip over our
alloc
trap within the implementation of
BNRItemStore
because the
super
keyword is only relevant to the class in which the method is implemented.

 

Now we have ensured that multiple instances of
BNRItemStore
cannot be created. We have also ensured that once the instance of
BNRItemStore
is created, it is never destroyed because a static variable (that never gets destroyed) always maintains ownership of it.

 

In
BNRItemStore.h
, give
BNRItemStore
an instance variable to hold an array of
BNRItem
instances and declare two more methods:

 
#import
@class BNRItem;
@interface BNRItemStore : NSObject
{
    NSMutableArray *allItems;
}
+ (BNRItemStore *)sharedStore;
- (NSArray *)allItems;
- (BNRItem *)createItem;
@end

See the
@class
directive? That tells the compiler that there is a
BNRItem
class and that it doesn’t need to know this class’s details in the current file. This allows us to use the
BNRItem
symbol in the declaration of
createItem
without importing
BNRItem.h
. Using the
@class
directive can speed up compile times considerably because fewer files have to be recompiled when one file changes. (Wonder why? Flip back and read
the section called “For the More Curious: Build Phases, Compiler Errors, and Linker Errors”
.)

 

In files that actually send messages to the
BNRItem
class or instances of it, you must import the file it was declared in so that the compiler will have all of its details. At the top of
BNRItemStore.m
, import
BNRItem.h
.

 
#import "BNRItemStore.h"
#import "BNRItem.h"
 

In
BNRItemStore.m
, override
init
to create an instance of
NSMutableArray
and assign it to the instance variable.

 
- (id)init
{
    self = [super init];
    if (self) {
        allItems = [[NSMutableArray alloc] init];
    }
    return self;
}
 

Now implement the two methods in
BNRItemStore.m
.

 
- (NSArray *)allItems
{
    return allItems;
}
- (BNRItem *)createItem
{
    BNRItem *p = [BNRItem randomItem];
    [allItems addObject:p];
    return p;
}
 

Other books

That God Won't Hunt by Sizemore, Susan
The Good, the Bad & the Beagle by Catherine Lloyd Burns
Mislaid by Nell Zink
An Affair Most Wicked by Julianne Maclean