Reputation: 187
I am developing my first iPhone app and I am making a call to an API to return some JSON which populates different elements of my UI. Currently I have implemented a synchronous method in a helper class which I call in the viewDidLoad method.
-(NSDictionary*) dataRequest: (NSString*)url withMethod:(NSString*)method
{
NSError *e;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:url]];
[request setHTTPMethod:method];
NSURLResponse *requestResponse;
NSData *requestHandler = [NSURLConnection sendSynchronousRequest:request returningResponse:&requestResponse error:nil];
NSDictionary *json = [NSJSONSerialization JSONObjectWithData: requestHandler options: NSJSONReadingMutableContainers error: &e];
return json;
}
The problem here is I lock up my UI, I tried to implement this asynchronously but the data gets returned long after the code attempts to populate the UI elements what is the best and correct way to implement asynchronous calls and populate my UI elements with the correct data?
Upvotes: 0
Views: 4121
Reputation: 1751
There are some cases when making a synchronous request makes sense, but not if the view controller is waiting (blocking the main thread) while the synchronous request completes. I have made a blog post to help answer this question in depth: http://jasoncross-ios-development.blogspot.com/2015/04/asynchronous-json-requests-in-objective.html. The post also discusses other issues surrounding consuming JSON from a web server for an iOS application: which design patterns to consider under different circumstances.
One of the most common patterns in iOS development is the display of data which originates from a Web Service and is transmitted via JSON. This post discusses synchronous and asynchronous fetching of data, and other related design patterns.
What is the difference between synchronous and asynchronous http requests? In a synchronous http request, the currently executing code "blocks" (stops and waits) until the request finishes. If this code is on the main thread, then the app will appear frozen and broken while the network call is made. This is a bad idea, is against best practices, and should always be avoided.
In contrast, an asynchronous http request allows the currently executing code to continue while the request is initiated and while the request is running. Once the request completes, the code "reports back" to a listener. In the case of NSURLConnection sendAsynchronousRequest:queue:completionHandler:
, the listener is the caller of the method who passes in a block as a method argument. In the case of network requests being made from the main thread, the User Interface does not lock, the application continues to respond to user gestures, and is the accepted best practice.
In a nutshell, that's all you need to know: use the asynchronous method. But that begs the question: why is the synchronous method even available if it should never be used? The answer is that there are many scenarios when making a synchronous request makes sense. Detailing some of these scenarios comprises the remainder of the blog post.
Consider using a "Service Layer" to make the requests, as follows:
model classes. These often mirror the JSON objects service layer. These classes know about the API and make network calls to fetch JSON. When the network calls complete, JSON is converted to native model classes (Objective-C objects). View controller. These classes handle user interaction and ask for data from the service layer as needed.
If your iOS application only needs to fetch data once and is not concerned about updates, the service layer can perform an asynchronous request, and update the caller when the request complete, using a block. The caller may ask the service layer for data as per below:
[serviceLayer fetchDataFromAPIWithResponseBlock:^(NSArray * arrayOfObjects, NSError *error) {
if (nil != error) {
// deal with error appropriately
}
else if (nil != arrayOfObjects) {
// update the user interface
}
}];
But if your application is concerned with data that changes, either on the server or on the client, then you will want a way to coordinate model changes with updates to the User Interface. In this case, you should consider using either the delegate design pattern or the observer design pattern. If using a delegate pattern, the View Controller would be a delegate of the Service Layer. When the model data changes, the service layer informs the delegate. In this case, the service layer could use a synchronous request because the View Controller is not blocking while it waits to hear back from the service layer. An alternative to implementing the delegate pattern is to use the NSNotificationCenter to inform the View Controller when the model data changes. The biggest deciding factor between using the delegate pattern and Notifications is how many view controller need to be informed when the model data changes. If there is only one view controller, it can be made a delegate. If there are more than one view controller, they can all be informed via Notifications.
If individual properties of model objects are being changed (possibly from elsewhere in the iOS application), then you should consider using Key Value Observing (KVO) to update the views when the model changes.
More in depth discussion may be found at the blog post mentioned above.
Upvotes: 0
Reputation: 62676
It's expected that the data gets returned long after the request is made (long, relative to nearly instantaneous execution of the next line of code after the send request). The trick is to defer the update of the UI until the request completes.
// optionally update the UI to say 'busy', e.g. placeholders or activity
// indicators in parts that are incomplete until the response arrives
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// optionally update the UI to say 'done'
if (!error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData: requestHandler options: NSJSONReadingMutableContainers error: &e];
// update the UI here (and only here to the extent it depends on the json)
} else {
// update the UI to indicate error
}
}];
Even more abstractly -- and more correctly -- consider that the data being fetched is likely part of the app's model. A fetch from the server is just one cause of change to the model. When the model is changed for any reason, either through user action or this fetch or some other event, it is the view controller's job to observe that it changed, and tell the views to update.
Upvotes: 4