The term "lazy-loading" might not be entirely well suited to a single JSON response, and it might predispose us to one approach over another. So let me tackle what I presume is the underlying question, that you want to achieve improved user experience despite a very large server database:
If the JSON is really large, you could always contemplate a "paging" metaphor, where you download the first x records, and then make subsequent requests for the next "pages" of server data. Given that additional server records can presumably be added between requests, you'll have to think carefully about that implementation, but it's probably easy to do.
Another way of making the JSON more efficient, is to limit the data returned in the initial request. For example, the first request could return just identifiers or header information for all or a reasonable subset of the records, and then you could have subsequent requests for additional details (e.g. the user goes to a detail screen).
A particular permutation of the prior point would be if the JSON is currently including any large data elements (e.g. Base64-encoded binary data such as images or sound files). By getting those out of the initial response, that will address many issues. And if you could return URLs for this binary data, rather than the data itself, that would definitely lend itself for lazy loading (as well as giving you the ability to easy download the binary information rather than a Base64 encoding which is 33% larger). And if you're dealing with images, you might want to also contemplate the idea of thumbnails v large images, handling the latter, in particular, in a lazy-loading model.
You could contemplate implementing a version of XML parsing that supports streaming. The standard NSXMLParser
implementations try to load the entire XML feed (or as much as possible?) into memory at one time (despite method names that would suggest the contrary) before parsing proceeds. If your app used LibXML2
(such as in Apple's AdvancedURLConnections sample), you can continue downloading and parsing the XML in the background while the initial data is presented to the user. This will simultaneously yield the benefit that most people associate with "lazy loading" (i.e. you don't wait for everything before presenting the results to the user) and "eager loading" (i.e. it will be downloading the next records so they're already ready for the user when they go there).
For us to make more intelligent suggestions, you really need to share more information about the nature of your JSON and the data behind it, describe why you think "lazy loading" is the solution, etc. You might not want to go nuts on a particular solution until you do some analysis of the data (e.g. a JSON for thousands of rows can still be smaller than a single large image).
Update:
If you were going to adopt the first approach, you first need to change your web service so that in response to a request, it only delivers n records, starting at at a particular record number. You probably also need to return the total number of records on the server so you can give the user some visual cues as to how much there is to scroll through.
Second, you need to update your iOS app to support this model. Thus, in your UI, as you're scrolling through the results, you probably want your user interface to respond to scrolling events by showing either (a) actual results if you've already fetched it; or (b) some UI that visually indicates that the record in question is being retrieved and then asynchronously request the information from the server. If you were doing this in a UITableViewCell
, it might do something like:
- (void)viewDidLoad
{
[super viewDidLoad];
[self initiateRequestForFirstSetOfData];
}
- (void)initiateRequestForFirstSetOfData
{
// asynchronously retrieve the data from the server:
// (a) retrieve the total number of records
// (b) retrieve the actual data for the first n records
// and when this is done, dispatch a `[self.tableView reloadData]` back to the
// main queue.
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.totalNumberOfRecordsOnServer;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
BOOL isAvailable = ... // do whatever logic to determine if this row has already been retrieved
if (isAvailable)
{
// configure the cell like normal here
}
else
{
// configure the cell to indicate that a fetch is in progress here.
// perhaps add a UIActivityIndicatorView and startAnimating it
dispatch_async(backgroundQueue, ^{
// initiate the request for the data (if you haven't already)
dispatch_async(dispatch_get_main_queue(), ^{
// don't just update the UI here, but make sure the cell
// in question is still on screen by calling `UITableView`
// method `cellForRowAtIndexPath`, not to be confused with
// this method, which is a `UITableViewController` method.
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (cell)
{
// update the cell: sometimes you can get away with
// updating the cell directly, sometimes you want to
// just call something like:
//
// [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
});
});
}
cell.imageView.contentMode = UIViewContentModeScaleAspectFill;
return cell;
}
There are a whole bunch of refinements I might suggest over the above, fairly simplistic code, but hopefully it gives you the basic idea. You (a) figure out how many total rows of data there are; (b) you retrieve the first n records; (c) as the user scrolls to a record, you show it if you've got it, but if not, provide visually cue that you're going to get that data for them asynchronously; and (d) when the data comes in, update the UI, if appropriate.
Like I said, I'd probably pursue some refinements over the above code, e.g., I'd probably not embed the asynchronous retrieval in the view controller itself but rather do that in my model and have some delegate pattern to update the UI, I'd probably use operations queue rather than dispatch queue so I could cancel requests that we not needed any more, etc, but you get the basic idea.
If you're using a UICollectionViewController
, it's analogous to the above code. If you're using a scroll view, the pattern is very similar, though you'd be responding to the UIScrollViewDelegate
method scrollViewDidScroll
instead (and you'd need to write code not only like the above, but also something that would release UIKit
elements that had scrolled off the screen, too).