Myapp uses a remote API to update its data, and it has to work offline, so all the data has to be downloaded before showing the main section of the app.
The API returns one big JSON structure representing the content of the app. The JSON is converted to many dictionaries and arrays, then model classes are instantiated and are backed by these dictionaries and arrays.
My problem is that some of these dictionaries and arrays contain URLs for images that also have to be downloaded. Should it be the responsibility of each model object to download its own images? Or should I have some kind of controller class that handles this?
Also, what would be a good approach to handle this? For example, how do I know when all model objects have finished downloading their resources?
All the resources must be uploaded upfront as the client would like to be able to use the app without an internet connection.
The answer depends upon the business requirements of your app.
Often one will download the JSON data up front, but defer the loading of the images, not downloading them until required by the UI (a pattern known as "lazy loading"). One particularly elegant way of handling that is to use a UIImageView
category that performs just-in-time asynchronous image retrieval (SDWebImage is a pretty good implementation; if you're using AFNetworking, it has one, too). For both of those, you'd simply do something like:
[self.imageView setImageWithURL:url];
And that will retrieve the image asynchronously, cache the image in case your UI needs it again, etc. Also, if you use this within a tableview with cell reuse, it will cancel old requests for prior cells for which the download hasn't yet finished to make sure that requests for the images for visible cells don't get backlogged behind a bunch of old requests that the user may no longer care about, etc.
Sometimes, though, you really want to retrieve everything up front (e.g. you're downloading the content for a magazine that you want to make available for reading offline).
In that case, rather than having the model, itself, do the retrieval, you might have a separate data controller which will download and parse the results. If you need to inform the UI when this is done, you might employ local notifications, to do so.
While this latter pattern might seem appealing at first glance, it has numerous disadvantages. If, for example, the user is on their cellular connection, you might spend a lot of bandwidth downloading images that the user might not immediately need, consuming the user's data plan and battery in the process. So you might want to only do this if (a) you really need everything up front for offline usage; and (b) you're ok with the resource (data plan and battery consumption) drain this entails.
You will probably want a download manager class (separate from your models) to take care of downloading the images. Your manager class will manage an operation queue of operations, each of which downloads the assets for a given model object. Using AFNetworking will make all of this very easy. You can then run these operations in the background, letting your user continue to use the UI while they download.
Your download manager will create an AFHTTPClient
, loop through your models creating an an array of AFImageRequestOperation
s, one per model (or one per download, if there are multiple images per model). Pass your array of operations to enqueueBatchOfHTTPRequestOperations: progressBlock: completionBlock:
on your HTTP client. Your completion block will be invoked when all download operations have finished.
This download process should be run once you have all of your models from the JSON, to ensure that you have all of the data you need to get all of your assets.
EDIT: Worthy to note, if you're only supporting iOS 7, AFNetworking's AFHTTPSessionManager
would be more appropriate. The suggestions I have provided will work on iOS 7 and are supported on iOS 6 as well.