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
There is a slight issue here, although you can’t see it. When
ListViewController
makes this request, it expects some time to pass before the request is completed – this is why it supplies a callback block to be executed when the request is finished. When the store decides to return cached data, the callback is executed immediately. This can cause an issue if the
ListViewController
has more code in
fetchEntries
after it makes the request because it expects that code to be executed before the completion block.
Let’s put in a two log statements in
ListViewController.m
to confirm this behavior.
Build and run the application and tap on the
Apple
segment. Notice that
Completion block called!
shows up in the console before the log statement at the end of
fetchEntries
when the data is cached. If the cache is out of date and fetched again, the order of the statements is reversed. The store should really be consistent about when it executes the callback for the request. In
BNRFeedStore.m
, add the following code to
fetchTopSongs:withCompletion:
.
Now when you build and run the application, the callback is always executed after
fetchEntries
completes because the completion block is inserted as the next event in the run loop instead of being called immediately.
The caching mechanism for Apple’s top songs feed is pretty simple: each time a new feed is fetched, it completely replaces the old feed in the cache. This makes sense for this particular feed – we aren’t maintaining a history of top songs, just showing the current top ten.
The Big Nerd Ranch forum feed is a little different. Every time a new feed is fetched, you are getting the last day’s worth of posts. If we cached this feed in the same way as Apple’s feed, we wouldn’t be able to maintain a history. Also, the forum feed can be pretty large since we get a lot of posts in a day, so it takes a few moments to load the feed. It would be better for the user if they could see a list of cached posts as soon as they open the application or switch back to the
BNR
feed. When new posts become available, they would be added to the top of the list.
Figure 29.2 Cache and update flow
We can accomplish this task by implementing caching for the Big Nerd Ranch forum feed in a different way than we did for the Apple feed. For BNR’s feed, each time a controller requests that the store fetch new items, the store will immediately return the cache and then ask the server for more items. When the items come back from the server, the store will merge the new items into its cached
RSSChannel
. Then, the store will inform the controller that the new items have been fetched so that the interface can be updated. This interaction is shown in
Figure 29.2
.
To pull this off, we need to do three things:
Each of these steps requires a few small steps to achieve. We will start by teaching the
RSSChannel
to filter new items into its existing
items
array. First, declare a new method in
RSSChannel.h
.
When the store is asked to fetch the RSS feed, we want it to unarchive the cached channel from disk, return it, and ask the server for another
RSSChannel
instance that has the new items in it. When the request completes, the cached channel should be sent
addItemsFromChannel:
with the new channel as an argument. So we want this method to add items from the new channel to the existing channel. One little problem: because the RSS feed returns all items from the last 24 hours, if you refresh the feed more than once a day, you will get duplicates.
The channel, then, has to determine whether it already has an item before adding it to its list. To do this, instances of
RSSItem
have to be checked for equality. Every
NSObject
subclass implements a method named
isEqual:
for this purpose. In
RSSItem.m
, override this method to make two
RSSItem
s equivalent if they have the same
link
.
In
RSSChannel.m
, begin implementing
addItemsFromChannel:
so that it adds all items from another channel if there are no duplicates.
Even though you do not use
RSSItem
’s
isEqual:
method explicitly,
NSArray
’s
containsObject:
does. The
containsObject:
method sends
isEqual:
to every object within the array, and if one responds
YES
, then
containsObject:
also returns
YES
. By inverting the return value of
containsObject:
,
addItemsFromChannel:
will only add an item if it doesn’t already have it.
Next, the
RSSChannel
needs to preserve the order of its items. The order of items should be dictated by when the item was posted – the newer an item is, the higher it should appear on the list. The post date of an item is available in the RSS feed, but
RSSItem
doesn’t currently collect that data. Let’s change that. In
RSSItem.h
, declare a new property to hold the post date.
In
RSSItem.m
, synthesize this property and have the XML parsing methods seek out the appropriate element and put it into this property.
(Wondering where we are pulling these crazy date formats from? The specification for the date format string is described at
http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns
. You can log the
pubDate
to the console to see its format.)
Of course, the
publicationDate
is something that needs to be cached with each
RSSItem
so that we can preserve the order of items the next time the application is run. Add code to the
NSCoding
methods in
RSSItem.m
to do this.
Now an
RSSItem
can be ordered amongst its peers by publication date. In
RSSChannel.m
, modify
addItemsFromChannel:
to reorder its items.
The method
sortUsingComparator:
is a nifty tool for sorting an
NSMutableArray
. This method takes a block that takes two object pointers as an argument and returns an
NSComparisonResult
. (An
NSComparisonResult
is a data type whose value can tell us the sorted order of two objects.) When this method executes, it compares each item in the array by passing two of them at a time as arguments to the supplied block. The block returns which of the two objects is ordered before the other as an
NSComparisonResult
.
The
NSDate
method
compare:
compares two
NSDate
instances and returns the one that is ordered before the other as an
NSComparisonResult
. The return value of
compare:
is the return value of the comparator block.
Now that a channel can update its
items
array when given a new channel, let’s have the store take advantage of this. In
BNRFeedStore.h
, change the return type of
fetchRSSFeedWithCompletion:
.
In
BNRFeedStore.m
, update the completion block for
fetchRSSFeedWithCompletion:
to merge the incoming channel with the existing channel and cache it.