The overriding issue in developing an iOS application – or any client application – is that you have a very limited amount of disk space.

And I’m not talking about the difference between a 32GB iPad and a 250GB desktop computer, I’m talking about the fact that the applications we really seem to care about have access to a seemingly infinite amount of data. An iOS application like Facebook, for example, has the ability to access so much data that it couldn’t ever fit on a single drive, much less your iPhone.

Therefore, the Facebook iOS application cannot possibly download and reconstruct the entire model graph that lives on Facebook’s servers. Which means that the concept I talk about in my last post – having a view controller hang onto the root model object and then chopping off its components to pass to other, more focused, view controllers – becomes more complicated.

This is where architecting iOS data flow becomes crucial. In applications of this nature we must fetch data only as needed. For example, when I log in to Facebook, I send my username and password and get back data that identifies the appropriate user. However, this data is fairly limited: it doesn’t have all of the photos I’ve been tagged in or all of the user data that represents each of my friends. Instead, all I get back is some basic information like my name, maybe the school I went to (Go Badgers!) and most importantly, a unique identifier for this user data.

This, of course, is intentional. If logging in fetched all of a user’s photos and friends, then each one of those friend photos would be fetched, and all of their friends, and all of their friend’s photos, and so on, until finally we’ve moved the entirety of Facebook’s data onto to our iPhone. We’re all Kevin Bacon these days, apparently.

After fetching the user data, we would construct an Objective-C object that represents the User that is populated with the information we were given upon logging in, but that is the extent of our model object graph at this point. If we wanted to present the list of a User’s friends in a view controller, we are now faced with a problem: we don’t have a list of friends in memory.

In a simple iOS application, the User object would have a pointer to an NSArray containing other User objects that represented each friend. The keyword here is “a pointer”, that is, direct access to friends. We don’t have that direct access to the friends here, instead, we have to call back out to the web server to grab that data before we can present it.

This problem is where the confusion sets and all kinds of wayward solutions creep up. When in reality, we are still in the exact same position as we were if all of the data was locally organized in a tree – even though it is remotely organized in a web. We still need to have a pointer to an array of friends in order to fit the architecture of our application (which is breaking off components of your model object graph to pass to controllers that operate on them).

If you know anything about Core Data, you know it solves a similar problem. It uses SQLite to store its data, so it, like Facebook’s database, has a web-like object graph. When you fetch an object from Core Data, you don’t have a pointer to every object that it is connected to right away. Instead, the fetched object has pointers to faults.

For example, let’s say you fetched a User object from Core Data. Its friends property should point at an NSArray of other User objects, but instead, it points at a fault. A fault, here, is an object that knows the unique ID of the friends array in the database, but isn’t the friends array itself. This conserves the amount of memory we have loaded in the application, both because the fault itself is small and more importantly, because loading a list of friends would trigger loading their list of friends and so on.

When you access the friends property of the User object, the fault automatically goes back to the managed object context and replaces itself with the actual list of friends stored in Core Data (conceptually, at least; each friend is originally a fault, too). To us, this mechanism is transparent, but in understanding it, we can create the appropriate solution for our own applications that pull data from a web service.

In the Facebook application, we call a web service that fetch a list of friends given a unique ID of a user. This is very much like a fault – we have the information necessary to get the friends, but we don’t have the friends yet. However, pulling data from a web server is a lot more involved than grabbing data from SQLite – it takes longer and may fail. Thus, we cannot obscure the fetch in a simple property access like Core Data does because things may not work or we would stall the main thread. We must be explicit about what we are asking for and handle any errors appropriately.

This is where Store objects come in. I’ve written extensively about Store objects in my book, but for those of you that haven’t read it: it is a singleton that creates model objects from data stored in an external source of data (like a web server or the filesystem) by request of controller objects. A method on a store that would fetch a list of friends would look like this:

- (void)fetchFriendsForUser:(User *)u
                 completion:(void (^)(NSArray *friends, NSError *err))block;

 

 

A view controller that wants to display a list of friends would need a pointer to a User object. Thus, the view controller that has the User would pass it along to the FriendsViewController:

- (void)showFriendsList
{
     FriendsViewController *fvc = [[FriendsViewController alloc] initWithUser:[self user]];
     [self presentViewController:fvc animated:YES completion:nil];
}

 

The FriendsViewController, it would fetch the list of friends when it appeared:

- (void)viewWillAppear:(BOOL)animated
{
    [[FacebookStore store] fetchFriendsForUser:[self user]
                                    completion:^(NSArray *friends, NSError *err) {
         if(!err)
            [[self tableView] reloadData];
         else
            [self showError:err];
    }];
}

 

The store would either fetch the list of friends for that user from the web server or use a cached list of friends. The store would then set the friends property of the User to the array of friends it received, and the FriendsViewController, having access to that user, would be able to display those friends:

- (int)tableView:(UITableView *)tv numberOfRowsInSection:(int)section
{
    return [[[self user] friends] count];
}

 

The beauty of this is that the Store made decisions about using cached/fresh data (or maybe it had already loaded the friends into RAM previously and did nothing), but according to our controller, we just have a tree of objects as we normally do. The only difference between this pattern and what I discussed in the last post is that controllers have a pointer to an object that is one level above the data that it is displaying if they need to fetch more data.

Of course, if you want to drill down into the data any further – like look at a particular friend’s profile – you don’t necessarily need to know that this friend came from this user’s list of friends. Here, you can simply pass the friend object along.

We are still remaining consistent with passing only the necessary model objects to view controllers, even though we are passing more than we would if conditions were different. And, we are still using a tree of objects, even though the real object graph is a giant web. Just like some view controllers only see a subgraph of objects in an application’s model graph, an application only sees a subgraph of objects in a database’s model graph.

Now, I would be remiss if I didn’t mention that this pattern has some useful variations to it. For example, I like for the Store to hang onto the root model object, which is most cases, is the user object. That way, I don’t have to continue to pass the User object around, the Store always knows the current user.

Another variation of this is more closely models the concept of faults. Instead of passing the User to the FriendsViewController, I would pass a “fault” that represents the friends. Then, the FriendsViewController would not need access to the User – instead, that fault would essentially contain an empty array of friends and would have access to the User (it’s parent in the tree). When the FriendsViewController appeared, it would pass the “fault” to the Store, which would be able to unpack the necessary data to make the service request.

Joe Conway

Founder at Stable Kernel

Leave a Reply

Your email address will not be published. Required fields are marked *