iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (96 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.97Mb size Format: txt, pdf, ePub
 

Let’s follow the logic of this method. When the request is made, the cached
RSSChannel
is loaded from the filesystem. A second, empty instance of
RSSChannel
is set as the
xmlRootObject
of the connection. When the service completes, the
xmlRootObject
will contain the new items from the server. This channel is then merged into the existing
cachedChannel
(
Figure 29.3
). The merged channel is cached and passed to the controller via its completion block. At the end of this method, the cached channel (before being merged) is returned to the controller.

 

Figure 29.3  Merging RSSChannels

 

Now you can update
fetchEntries
in
ListViewController.m
to grab the channel from the return value of
fetchRSSFeedWithCompletion:
and update the table right away.

 
if (rssType == ListViewControllerRSSTypeBNR)
{
    channel =
[[BNRFeedStore sharedStore]
                    fetchRSSFeedWithCompletion:completionBlock];
    
[[self tableView] reloadData];
}
 else if (rssType == ListViewControllerRSSTypeApple)
    [[BNRFeedStore sharedStore] fetchTopSongs:10 withCompletion:completionBlock];
 

Build and run the application and then load the
BNR
feed. Go to the forums and create a new post. (You can create a test post in the forum for this chapter and delete it later.) Then, switch to the
Apple
feed and back to the
BNR
feed to force a reload. You will notice that your list remains intact, and after the activity indicator finishes spinning, your post will appear at the top of the list!

 

Now this is all fine and dandy, but there is still a problem. Right now, the store returns an instance of
RSSChannel
to the
ListViewController
immediately. In the store’s completion block, the other
RSSChannel
merges its
items
into the
very same
RSSChannel
that was returned to the
ListViewController
. This is bad. A store shouldn’t update values it has already given to a controller without at least informing the controller of the changes and giving the controller the decision to act on those changes. Case in point: there is no way for the
ListViewController
to know which items are new by the time its completion block is called because its channel has already been updated, so it can’t indicate to the user which items are new.

 

To prevent this, we need a third
RSSChannel
instance (
Figure 29.4
). The first
RSSChannel
we use is an empty one for reading from the server, and the second is the cached one for returning to the controller immediately. The third will begin as a carbon-copy of the one returned to the
ListViewController
– the one that was in the cache. However, when the request completes, this carbon-copy will be merged with the new items, while the one the
ListViewController
has will not. The store will then give the merged channel to the
ListViewController
, and the
ListViewController
will compare it to the channel it already has. The
ListViewController
will then animate the new items into the table view, which will give the user a better idea of what just happened.

 

Figure 29.4  RSSChannels used by the store

 

One approach to create this carbon-copy would be to load the
RSSChannel
from the filesystem twice. However, this approach is lame: there is a performance penalty for loading from the disk, and there would be two copies of each
RSSItem
in memory (
Figure 29.5
). While it’s okay to have a few instances of
RSSChannel
(it’s a pretty small object), it’s not okay to have double the
RSSItem
s and suffer through loading the file twice.

 

Figure 29.5  Duplicate RSSItems in memory

 
NSCopying

Fortunately, there is an easier solution.
RSSChannel
can conform to
NSCopying
. When a class conforms to this protocol, you can create copies of its instances by sending the message
copy
to an existing instance. Copying a channel will mean that its
title
,
infoString
, and
items
array are copied into a new instance of
RSSChannel
.

 

Now, you may be thinking,

If you copy the
items
array, aren’t you just duplicating all of the
RSSItem
s in it?

Nope. An array only keeps references to the objects that it contains, so by copying an array, you are just copying the addresses of each object into the new array, not the objects themselves (
Figure 29.6
).

 

Figure 29.6  Copying an RSSChannel

 

In
RSSChannel.h
, declare that this class conforms to
NSCopying
.

 
@interface RSSChannel : NSObject
    , NSCopying
>
 

This protocol only has one method –
copyWithZone:
. When an object is sent
copy
, it really calls
copyWithZone:
and returns a new instance with the same values as the copied instance. (
copyWithZone:
is a relic like
allocWithZone:
. Zones don’t matter anymore – you implement the
withZone:
methods in your classes, but send messages without the
withZone:
part.)

 

In
RSSChannel.m
, implement
copyWithZone:
to return a new instance of
RSSChannel
.

 
- (id)copyWithZone:(NSZone *)zone
{
    RSSChannel *c = [[[self class] alloc] init];
    [c setTitle:[self title]];
    [c setInfoString:[self infoString]];
    c->items = [items mutableCopy];
    return c;
}
 

This method is almost entirely straightforward. A new channel is created, and it is given the same
title
and
infoString
as the receiver. Instead of sending
alloc
to
RSSChannel
, we send it to the class of the object being copied, just in case we subclass
RSSChannel
. (Without this, a copy of a subclass would be an instance of
RSSChannel
and not the subclass.)

 

The curious bit about this method is how the
items
array is set. We know that an object’s instance variables can be accessed directly within one of its methods. To access another object’s instance variable, though, you must use an accessor method. However, there is no setter method for a channel’s
items
instance variable, so the channel being copied can’t change the
items
array of its copy. But wait! An Objective-C object is a C structure. When you have a pointer to a structure, you can access its members using the arrow (
->
).

 

There are two caveats about accessing an instance variable directly. First, the setter method doesn’t get called.This causes a problem if the setter method performs some additional operations on the incoming value. So, if there is a setter method, you should use that instead.

 

Second, you can’t access an instance variable directly whenever you please. The only reason it works here is because you are within the implementation for
RSSChannel
, and the object whose instance variables you are directly accessing is an instance of
RSSChannel
, too. It wouldn’t work if, say,
ListViewController
tried to access an instance variable of the
RSSChannel
directly.

 

This is because instance variables of a class are
protected
from access by other objects. There are three visibility types for instance variables: protected (the default),
private
, and
public
. A protected instance variable can be accessed within the methods of the class it was declared in and all the methods of any subclasses. However, if a method from another class wants to access a protected instance variable, it must use an accessor method.

 

A private instance variable, on the other hand, can only be accessed within methods for the class it was declared in – not the methods of other classes or even methods of a subclass. A public instance variable can be accessed anywhere using the arrow notation. The syntax for declaring the visibility of instance variables looks like this:

 
@interface MyClass : NSObject
{
    @public
    NSObject *publicVariable;
    @private
    NSObject *privateVariable;
    @protected
    NSObject *protectedVariable;
}
 

In other languages, it is sometimes the case that you change the visibility of an instance variable to suit your needs. In Objective-C, there is rarely a reason to do this, so it’s really more trivia than anything. (The one case where
@private
is used is when framework developers write classes that will be subclassed. This prevents the subclass from modifying a variable directly, and is typically done because the code in the setter method for that variable needs to be executed for things to work out okay. Public instance variables are generally considered a bad idea.)

 

Other books

Agent with a History by Guy Stanton III
Coffee, Tea, or Murder? by Jessica Fletcher
Redemption by Will Jordan
HardJustice by Elizabeth Lapthorne
Dream Warrior by Sherrilyn Kenyon
The Day of the Storm by Rosamunde Pilcher
Eye of the Red Tsar by Sam Eastland
Dreaming the Bull by Manda Scott