2

I'm using a web service that returns a JSON formated data from a database on my server, this data will be displayed in some view on my iPhone app.

Since the app is free so I get thousands of rows on my DB and so of course in the JSON data I have to download.

How can I do a lazy loading for my JSON data? please any code lines will be very appreciated.

Thanks.

EDIT 1 :

in other words, I'm using an Sql request such as "select all from table", then returns this data as JSON to my app.

How can I request a little number then add others etc .. ?

hafedh
  • 539
  • 1
  • 9
  • 26
  • This has nothing to do with Xcode. –  Dec 20 '12 at 21:06
  • 2
    Clearly, we need a Winter Bash hat for removing 'xcode' tags... make it look like @H2CO3's avatar. :) Anyway, this sounds like something you need to think about in the server side design of your app. What is it doing that it needs to return thousands of rows at once to a client request? Perhaps the client request can be "paged"; i.e. ask for first 100 rows, then next 100 once user needs them, etc. – rickster Dec 20 '12 at 22:58
  • @rickster How many downvotes do you think I'd get if I posted this as a feature request on Meta? :P –  Dec 20 '12 at 23:05
  • Thanhs @rickster, yes my data can be "paged", I'll edit my question with "code-level" details.. – hafedh Dec 21 '12 at 11:00
  • In terms of selecting a few records, see http://stackoverflow.com/questions/1891789/sql-select-first-10-rows-only – Rob Dec 21 '12 at 18:18

1 Answers1

3

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:

  1. 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.

  2. 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).

  3. 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.

  4. 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).

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Really a big thank @Rob for your detailed answer, yes I want to proceed with the first suggestion you written, since I'm a new-bie on IOS dev, would you mind explain more how to do this, I can't see how to download a few records then download the next .. – hafedh Dec 21 '12 at 11:07
  • @hafedh I've expanded my answer re option 1 – Rob Dec 21 '12 at 16:23
  • Sorry for any disturbance, I found a git repo that suits my needs very well, but didn't succes to modify to get data from my json url or xml webservice : https://github.com/nmondollot/NMPaginator ? – hafedh Dec 22 '12 at 09:35
  • just give me some time to see what you wrote and implement and I'll brb, very glad for your help.. keep connected :) – hafedh Dec 22 '12 at 17:46
  • @hafedh I took a look at that library. A little more complicated to implement than the author implied, but I got it to work. I've responded to your comment on that github site with details on what I had to do to make it work. It also presumes you've got your web service working (do you?). I've expanded that answer a bit, with further refinements and answers to your questions, so feel free to take another look at it. – Rob Dec 22 '12 at 19:18