iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (98 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)
9.19Mb size Format: txt, pdf, ePub
 
- (BOOL)hasItemBeenRead:(RSSItem *)item
{
    // Create a request to fetch all Link's with the same urlString as
    // this items link
    NSFetchRequest *req = [[NSFetchRequest alloc] initWithEntityName:@"Link"];
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"urlString like %@",
                         [item link]];
    [req setPredicate:pred];
    // If there is at least one Link, then this item has been read before
    NSArray *entries = [context executeFetchRequest:req error:nil];
    if ([entries count] > 0)
        return YES;
    // If Core Data has never seen this link, then it hasn't been read
    return NO;
}
 

You can build and run now, and the application will work. However, the
ListViewController
does not yet use these methods to update its user interface or tell the store when an item has been read. In
ListViewController.m
, add code to the end of
tableView:didSelectRowAtIndexPath:
to mark an item as read when a row is tapped.

 
    RSSItem *entry = [[channel items] objectAtIndex:[indexPath row]];
    
    [[BNRFeedStore sharedStore] markItemAsRead:entry];
    // Immediately add a checkmark to this row
    [[[self tableView] cellForRowAtIndexPath:indexPath]
                    setAccessoryType:UITableViewCellAccessoryCheckmark];
    
    [webViewController listViewController:self handleObject:entry];
}
 

Then, modify
tableView:cellForRowAtIndexPath:
in
ListViewController.m
to put a checkmark on each row whose
RSSItem
has been read.

 
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView
                            dequeueReusableCellWithIdentifier:@"UITableViewCell"];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                       reuseIdentifier:@"UITableViewCell"];
    }
    RSSItem *item = [[channel items] objectAtIndex:[indexPath row]];
    [[cell textLabel] setText:[item title]];
    
    if ([[BNRFeedStore sharedStore] hasItemBeenRead:item]) {
        [cell setAccessoryType:UITableViewCellAccessoryCheckmark];
    } else {
        [cell setAccessoryType:UITableViewCellAccessoryNone];
    }
    return cell;
}
 

Build and run the application. Select an item from the table view and note that a checkmark appears in that row. You can shut down the application and load it again – the checkmarks will still be there.

 
Other Benefits of Store Objects

Now that you’ve changed
Nerdfeed
to use a store and added new features involving external sources of data, let’s return to the general concept of store objects and the benefits of using them.

 

The main selling point of store objects is that we move the burden of dealing with external sources of data away from controllers and put all the request logic in one place. This is reason enough to use MVCS, but we’ve found other benefits of using store objects that we’d like to share.

 

We can write the controller logic of an application independently of its store logic. This really carries two benefits because the controller and store logic are independent in time and in space.

 

When beginning a project, sometimes the external sources aren’t ready for primetime, and the store logic can’t be finalized until the sources are ready. For example, if the external source is a web server, the web service calls may not be able to be written until the project is nearly complete.

 

Using stores prevents this situation from becoming a scheduling bottleneck. A store object can expose all of its methods to controller objects so that the rest of the application knows what to expect. In addition, the store’s actual implementation can provide interim fake data to controllers that make requests.

 

Once the external source is finalized, the store’s implementation can be updated to handle the final details. The good news for the project is, at this point, only the store object must be changed; the rest of the application will work as-is.

 

Independent store and controller logic also means you can have programmers working independently on each. As long as the interface between them is simple and (ideally) remains unchanged, the programmers that write the store and the programmers that write the controller can work in parallel. This can speed up your project’s development.

 

Typically, the code in a store object is more difficult to write than the code in the controller. Most developers can wire up a controller and its XIB file, but it takes experience to write a good store object. You have to know how to write flexible and robust code. (It also helps to have been through the wringer a few times and seen how things go wrong and how to fix them.) At Big Nerd Ranch, we typically have our senior-level people write store objects and our junior-level people write the controllers and user interface aspects of an application. Smart junior-level people pick up on how the store works and end up becoming senior-level people themselves.

 

Stores also give the application a unified point of contact with an external source. In addition to things like caching and storing user credentials for web servers that we’ve already seen or mentioned, there is another bonus: it is easy to test what happens when an application is

offline.

Applications that do a lot of their work with a server aren’t guaranteed to have Internet access. An application of this type still needs to work – at least somewhat – while offline.

 

Normally, it is difficult to test what an application will do when it is offline. However, with store objects and their
BNRConnection
friends, it becomes totally simple. You write debug code in
BNRConnection
’s
start
method. When debugging, this method will send the
BNRConnection
the message
connection:didFailWithError:
instead of starting up the connection. This simulates what would occur if the connection actually failed without having to turn off your device or the simulator’s Internet connection over and over again.

 
Bronze Challenge: Pruning the Cache

The cache of
RSSItem
s for the BNR feed can get pretty big. Restrict the cache to 100 entries.

 
Silver Challenge: Favorites

Add an interface to mark an
RSSItem
as a favorite. Favorites should persist between runs of the application.

 
Gold Challenge: JSON Caching

Right now,
RSSChannel
and
RSSItem
instances are cached using the archiving data format. Make it so they can also be cached as JSON. (Hint: add another method to
JSONSerializable
for storing an object’s instance variables in a dictionary.) The
BNRFeedStore
should cache the Apple feed in JSON format and load it back from that JSON format.

 
For the More Curious: Designing a Store Object

You have built and used a number of different store objects in this book. In this section, we will analyze these differences and give you a guide for determining how to build store objects in your own applications.

 

When designing a store object you must answer these questions:

 
  • What external source(s) will it work with?
 
  • Should it be a singleton?
 
  • How will it deliver the results of a request?
 
Determining external sources

When designing a store, you must first consider what the application is trying to accomplish. How many external sources will there be and of what type? This, in turn, will help you determine how many store objects an application needs.

 

Should applications have one-to-one correspondences between store objects and external sources? We’ve seen that’s not the case. For example, in
Nerdfeed
, the store originally worked with one source – the BNR web server. Then we added a request to the iTunes web server. Even though these are two separate requests made to two separate sources, the code for interacting with them is very similar. So, it made sense to have a single store object handle both rather than have two objects with redundant code.

 

On the other hand, in
Homepwner
, you had two store objects: the
BNRItemStore
for fetching and saving items and the
BNRImageStore
for fetching and saving images. These two stores handle different types of data and requests. So the request logic was better kept separate in two objects. The
BNRImageStore
was meant to deal with saving and loading images – it wasn’t tied to the idea of a
BNRItem
or even
Homepwner
. Bundling it together with
BNRItemStore
would make for less tidy and reusable code.

 

Here are some best practices: If parts of an application’s request logic can work independently of the rest, then the logic should be split across multiple stores. On the other hand, if an application’s request logic contains chunks of similar code – even if it’s interacting with several external sources, then it is best to keep the logic in one tidy class. Doing so makes your code easier to read, maintain, debug, and reuse when possible.

 

Once you have determined how many stores an application should have and what external sources they will be dealing with, then you can start designing individual stores according to what they need to accomplish. For example, in
Nerdfeed
, the store needs to fetch two RSS feeds. So it must know how to work with a web server in general and the BNR and
iTunes
web servers in particular. Another example is the
BNRImageStore
in
Homepwner
. That store object needed to know how access and store images in a cache on the filesystem.

 
Determining singleton status

The next item on the agenda is whether a store should be a singleton or instantiable. The stores you’ve created have all been singletons, but you used a store object that was not: a
CLLocationManager
. What’s the difference? In
Whereami
, an instance of
CLLocationManager
s had options. It could tune how accurate it was or how often it updated a controller. If a store needs to offer these kinds of options, then it’s best to allow multiple instances of it to be created.

 

Another thing you should consider is whether a store needs to maintain a cache of data. If it does, then it should be a singleton so that every controller can have access to the same cache. This is the reason the stores you’ve created have been singletons.
BNRItemStore
,
BNRImageStore
, and
BNRFeedStore
all maintained caches of data.

 

You can think of login credentials for a web service as similar to a cache. Both are

data that a store is required to maintain.

It doesn’t make sense to make multiple instances of a store that maintains login credentials because you would be maintaining multiple copies of the same data.

 
Determining how to deliver results

The final option you must consider for store objects is how they deliver their response, and the first step to answering this question is determining whether a store will be synchronous (have its results available immediately) or asynchronous (need a little time to obtain its results). The best example of a synchronous store is an object that accesses the filesystem. The code may be complicated, but the request will be processed without any delay. If you have a synchronous store, you should have it deliver its response to the controller simply by returning a value from the request method. No muss, no fuss.

 

With asynchronous stores, there are more choices. The two big ones are delegation and blocks. The choice between these two depends partly on the store object’s singleton status. A singleton store should
not
use delegation because it would limit the store to returning data to one controller at a time. Instead, a singleton store can return its data in a block supplied by the requesting controller. A store that can be instantiated can use either delegation or blocks.

 

Other books

Subterranean by Jacob Gralnick
First by Chanda Stafford
Evan Blessed by Rhys Bowen
Falling Man by Don DeLillo
The Pendulum by Tarah Scott
Delta Ghost by Tim Stevens