Setting UILabel text taking longer than expected iOS7 - ios

In my view I attempt to display some weather related info. I use
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
Make NSURL and other related things
Send NSURLRequest
Set 2 UILabels to data
NSLog(Show data retrieved)
});
For some reason, I see the NSLog line roughly 15-45 seconds before the uilabel changes to the new text. I am fairly new to Obj-C and a lot of my code comes from using tutorial so I dont have the greatest understanding of the dispatch_async method. Any thoughts or suggestions would be appreciated.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLResponse *response = nil;
NSError *error = nil;
NSData *respData = nil;
NSURLRequest *forecastRequest = [NSURLRequest requestWithURL:currentForecastUrl];
respData = [NSURLConnection sendSynchronousRequest:forecastRequest returningResponse:&response error:&error];
[_responseData appendData:respData];
NSLog(#"Response: %#", response);
NSLog(#"Error: %#", error);
id JSON = [_responseData yajl_JSON];
currentForecast = [[NSArray alloc]initWithObjects:[JSON objectForKey:#"temperature"],[JSON objectForKey:#"feelsLike"],nil];
[_temperatureLabel setText:[NSString stringWithFormat:#"%#",[JSON objectForKey:#"temperature"]]];
[_tempDescriptionLabel setText:[NSString stringWithFormat:#"%#",[JSON objectForKey:#"desc"]]];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:2.0];
[_tempDescriptionLabel setAlpha:1];
[_temperatureLabel setAlpha:1];
[UIView commitAnimations];
NSLog(#"Done");
NSLog(#"JSON: %#", [JSON yajl_JSONStringWithOptions:YAJLGenOptionsBeautify indentString:#" "]);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
is used for create a background thread, but the user interface should be updated in the Main Thread
like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//do stuff in background
dispatch_async(dispatch_get_main_queue(), ^{
// Update your UI
});
});

All the "use the main queue" answers are good. But please notice that NSURLConnection does all this for you.
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// this happens on the main thread because we passed mainQueue
}];
EDIT: Here's your code, except smaller and with modern syntax. It should do exactly the same job. For fun, cut and paste this in.
// it assumes the following variables in scope
NSURL *currentForecastUrl;
__block NSArray *currentForecast;
UILabel *_temperatureLabel, *_tempDescriptionLabel;
// start counting lines here
NSURLRequest *forecastRequest = [NSURLRequest requestWithURL:currentForecastUrl];
[NSURLConnection sendAsynchronousRequest:forecastRequest queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// I cheated a little on lines, because we really ought to check errors
// in here.
NSError *parseError; // use native JSON parser
id parse = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&parseError];
currentForecast = #[parse[#"temperature"], parse[#"feelsLike"]]; // note the array and dictionary literal syntax
_temperatureLabel.text = parse[#"temperature"]; // if these are ivars, I reccomend using self.temperatureLabel, not _temp...
_tempDescriptionLabel.text = parse[#"desc"];
[UIView animateWithDuration:2.0 animations:^{ // use UIView animation block
_temperatureLabel.alpha = 1.0;
_tempDescriptionLabel.alpha = 1.0;
}];
}];
Deleting code for me is the most pleasurable part of programming. The line of code that's easiest to read, executes the quickest and requires no testing is the line that isn't there.

Your problem is because you're trying to update the UILabels off the main thread. I've seen that exact symptom before, where updating UI elements off the main thread still updates them, but with a long delay. So any time you have a UI element that's behaving in this way, check to make sure it's executing on the main thread.

I am surprised this code works at all because you are updating the label on a background thread. You want to do something along these lines:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{
//Make NSURL and other related things
//Send NSURLRequest
dispatch_async(dispatch_get_main_queue(), ^{
// Set your UILabels
});
});

Related

How to display UIActivityIndicatorView while fetching JSON data to be populated in UITableView?

I have a problem with my application.It freeze for several second when I tap the sidebar menu.
What happen when I tapped menu is I pass string that gonna be url for json data fetch in my mainviewcontroller.Then it freeze because I fetch the data and populating data in tableview.
However I really new to ios programming,I wonder how can I remove the freeze?.
thanks in advance
here is my code snippet for the mainviewcontroller:
Don't use dataWiyhContentsOfURL:, or at least not directly on the main thread. If you block the main thread then the whole app stops working (as you see).
You need to learn about background threads and callback blocks, and look at using NSURLSession to download your data and then process it.
Instead of using dataWithContentsOfURL (which will block the main thread and so the UI) you need to start an asynchronous connection. In the IF ELSE change the two requests to something like below. The completionHandler (Block) is executed when done, the data parsed, HUD removed and table Updated.
You can even (and in fact must) do this within your cellForRowAtIndexPath for each of the images, however, I would use SDWebImage as it has a cache and is very easy to use.
There are also other methods if this is not right for you such as NSURLSession.
Some other points;
I have also noted that the HUD is stopped on every iteration of the FOR and probably should be outside.
I also can not see how your data is being loaded so I added a [myTable reloadData];
I can not see that the "dictionary" object is needed as it can be added directly to the array (see code)
// If you have the status bar showing
// [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[HUD showUIBlockingIndicatorWithText:#"Please wait. . ."];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:kategori]];
[request setTimeoutInterval: 10.0];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
// [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (data != nil && error == nil)
{
//All Worked
id jsonObjects = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
for (NSDictionary *dataDict in jsonObjects)
{
NSString *title_data = [dataDict objectForKey:#"title"];
NSString *thumbnail_data = [dataDict objectForKey:#"thumb"];
NSString *author_data = [dataDict objectForKey:#"creator"];
NSString *link_data = [dataDict objectForKey:#"link"];
[myObject addObject:[[NSDictionary alloc]initWithObjectsAndKeys:
title_data, title,
thumbnail_data, thumbnail,
author_data,author,
link_data,link,
nil]];
}
[HUD hideUIBlockingIndicator];
[myTableView reloadData];
}
else
{
// There was an error
}
}];
For the images something like (this is not tested). I am not sure what format your images are in but you should be able to just add it, this may need tweeking;
cell.imageView.frame = CGRectMake(0, 0, 80, 70);
__block UIImageView *cellImage = cell.imageView;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[tmpDict objectForKey:thumbnail]]];
[request setTimeoutInterval: 10.0];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (data != nil && error == nil)
{
//All Worked
cellImage.image = [[UIImage alloc]initWithData:data];
[cellImage layoutIfNeeded];
}
else
{
// There was an error
}
}];
You can start activity indicator and call fetch data method after few time...
- (void)viewDidLoad{
[activityIndicator startAnimating];
[self performSelector:#selector(fetchData) withObject:nil afterDelay:0.5];
}
- (void)fetchData{
Fetch your data over here
}
Or ideally you have to load data Asynchronous
For loading data Asynchronously check out the following link-
iphone-synchronous-and-asynchronous-json-parse
I Prefer MBProgressHUD.
Here is the link for 3rd Party API.
https://github.com/jdg/MBProgressHUD
Just copy these two files in your app.
MBProgressHUD.h
MBProgressHUD.m

Why does app crash when setting a property with a __block identifier [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
Im trying to set a UITextView's text property with this code but I get a crash saying I can't do it from the main thread:
__block NSString *stringForText;
self.uploadTask = [upLoadSession uploadTaskWithRequest:request fromData:body completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// ...
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) response;
int errorCode = httpResponse.statusCode;
NSString *errorStatus = [NSString stringWithFormat:#"%d",errorCode];
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSString *totalResponse = [errorStatus stringByAppendingString:responseString];
stringForText = totalResponse;
[self updateView:stringForText];
// 4
self.uploadView.hidden = NO;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}];
// 5
[_uploadTask resume];
}
-(void)updateView:(NSString*)texto{
self.myTextView.text = texto;
}
Why does it crash saying I can't call it from the main thread in TestFlight?
Check again. You must update the UI from the main thread. It is likely your upload completion handler is operating off of the main thread. If you would like to call updateView: from any thread, then the you can dispatch the manipulation of the label to the main thread:
-(void)updateView:(NSString*)texto{
dispatch_async(dispatch_get_main_queue(), ^{
self.myTextView.text = texto;
});
}
Make sure that you're doing all of your work on the main thread. Once the upload completes, dispatch into the main queue:
__block NSString *stringForText;
self.uploadTask = [upLoadSession uploadTaskWithRequest:request fromData:body completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
// ...
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) response;
int errorCode = httpResponse.statusCode;
NSString *errorStatus = [NSString stringWithFormat:#"%d",errorCode];
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSString *totalResponse = [errorStatus stringByAppendingString:responseString];
stringForText = totalResponse;
[self updateView:stringForText];
// 4
self.uploadView.hidden = NO;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
});
}];
// 5
[_uploadTask resume];
It's crashing because of exactly that—your block is being called on a background thread and UI elements need to be modified on the main thread.
Wrap [self updateView:stringForText]; like dispatch_sync(dispatch_get_main_queue(), ^{ [self updateView:stringForText]; });

iOS: NSOperationQueue: Secondary queue

I would like to receive some data to the server in an asynchronous way and avoiding to overload the App UI performance. Hence would like to send tasks to the secondary queue and not the main one.
This is my current solution which uses the "main queue" ([NSOperationQueue mainQueue] which I understand slows down the performance):
-(NSDictionary*) fetchURL:(NSString*)url
{
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:kCONTACTSINFOURL]];
__block BOOL hasError = FALSE;
__block NSDictionary *json;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
//Verify type of connection error
json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
NSLog(#"Async JSON: %#", json);
}];
if (hasError) {
[[NSOperationQueue mainQueue] suspend];
return nil;
}
return json;
}
In order to use the secondary queue, and avoid overloading the UI and App performance, is it ok to allocate a shared NSOperationQueue and refer to that? Or is there some other "better" class or method to achieve this?
This would be my improved solution using a secondary NSOperationQueue:
Creating a secondary queue:
NSOperationQueue* otherQueue = [NSOperationQueue init];
Using the other (secondary) queue:
....
[NSURLConnection sendAsynchronousRequest:request
queue:otherQueue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
....
}
Is this correct? Or is there any other way to deal with this?
... following on from the comments...
The NSURLConnection method
[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]
allows you to specify the queue that the completion handler will be called on when the connection is complete. If you suspend this queue you will not stop the requests being sent, you will just stop getting callbacks when they are done. You may want to think about doing this slightly differently...
NSOperation is great, but I tend to prefer going straight for GCD (NSOperation is just a nice obj-c wrapper on the top) if you are hard-set on using NSOperations let me know and i'll add some advice for that.
I assume that you have some kind of manager class that handles all of your server communication? If not, I would recommend that you do, and have it as a singleton.
# interface ChatManager : NSObject
+ (ChatManager *)sharedManager;
#end
#implementation ChatManager {
dispatch_queue_t fetchQueue;
dispatch_queue_t postQueue;
}
+ (ChatManager *)sharedManager {
// This is just the standard apple pattern for creating a singleton
static SCAddressBookManager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[SCAddressBookManager alloc] init];
});
return sharedManager;
}
- (id)init {
self = [super init];
if (self) {
// Create a dispatch queue that will run requests one after another, you could make this concurrent but that may cause messages to be lost when you suspend
fetchQueue = dispatch_queue_create("fetch_queue", DISPATCH_QUEUE_SERIAL);
postQueue = dispatch_queue_create("post_queue", DISPATCH_QUEUE_SERIAL);
}
}
- (NSDictionary *)fetchURL:(NSURL *)url {
// In here we will dispatch to our queue and so that the
dispatch_async(fetchQueue, ^{
NSURLRequest = // create your request
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// Check for error
if (error) dispatch_suspend(fetchQueue); // this suspends the queue that is making the calls to the server, so will stop attempting to send messages when you have an error - you should start pinging to see when you come back online here too, and then use dispatch_resume(fetchQueue) to get it going again!
else {
json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"Async JSON: %#", json);
return json; // ??
}
});
}
- (NSDictionary *)postURL:(NSURL *)url data:(NSData *)bodyData {
// In here we will dispatch to our queue and so that the
dispatch_async(postQueue, ^{
/*
Make your post request.
*/
// Check for error
if (error) dispatch_suspend(postQueue);
else {
json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"Async JSON: %#", json);
return json; // ??
}
});
}
#end
You may also need to use
dispatch_sync(dispatch_get_main_queue(), ^{
// Do stuff on the main thread here
};
if you need to make delegate style callbacks to the main thread!
This is a basic idea on how I would have things setup to try and achieve what you are going for... feel free to let me know if any of this doesn't make sense to you..?
As a side note, I have assumed that for whatever reason you need to have a simple http interface for your server. The much better approach would be to have a persistent socket open between the app and your server, and then you can push data up and down at will. A socket with a heartbeat would also let you know when your connection has gone down. Not sure if maybe you would like me to elaborate on this option some more...

Adding UIActivityIndicatorView on Synchronous Web Service data and Populating UITableView

I am fetching the data from a web service by synchronous method. I make the request to the web service then view freezes. I try to add the UIActivityIndicatorView before loading the data from the web service and stopped it after getting the data but activity indicator is not displayed.
I tried to put the web service data fetch operations on the different thread
[NSThread detachNewThreadSelector:#selector(fetchRequest) toTarget:self withObject:nil];
but at this time TableView crashes as it does not get the data for drawing the cells.
in fetchRequest function I am doing
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL
URLWithString:URLString]];
NSData *response = [NSURLConnection sendSynchronousRequest:request
returningResponse:nil error:nil];
NSError *jsonParsingError = nil;
NSDictionary *tableData = [NSJSONSerialization JSONObjectWithData:response
options:0
error:&jsonParsingError];
responseArray = [[NSMutableArray alloc]initWithArray:[tableData objectForKey:#"data"]];
for(int i = 0; i < responseArray.count; i++)
{
NSArray * tempArray = responseArray[i];
responseArray[i] = [tempArray mutableCopy];
}
This responseArray is used to fill the information in the cell
Please tell me how to do this. Any help will be appreciated ...
The problem lies in your very approach. Synchronous methods run on the main thread. And because the UI updates on the main thread, your app hangs.
So, the solution would be using an asynchronous method to download the data on a separate thread, so that your UI won't hang.
So, use the NSURLConnection's sendAsynchronousRequest. Here's some sample code :
NSURL *url = [NSURL URLWithString:#"YOUR_URL_HERE"];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
//this is called once the download or whatever completes. So you can choose to populate the TableView or stopping the IndicatorView from a method call to an asynchronous method to do so.
}];
You should better use Grand Central Dispatch to fetch the data like this so you dispatch it in a background queue and do not block the main thread which is also used for UI updates:
dispatch_queue_t myqueue = dispatch_queue_create("myqueue", NULL);
dispatch_async(myqueue, ^(void) {
[self fetchRequest];
dispatch_async(dispatch_get_main_queue(), ^{
// Update UI on main queue
[self.tableView reloadData];
});
});
Regarding the Activity indicator you can use in the start of the parsing:
[self.activityIndicator startAnimating];
self.activityIndicator.hidesWhenStopped = YES
And then when your table is filled with data:
[self.activityIndicator stopAnimating];

Tracking Asynchronous Connections with Blocks

I am doing a lot of URL requests (about 60 small images) and I have started to do them Asynchronously. My code adds another image (little downloading thing) and then sets a Request going.
When the request is done I want "data" to be put in the location which was originally added for it, however, I can not see how to pass "imageLocation" to the block for it to store the image in the correct location.
I have replaced the 3rd line with below which seems to work but I am not 100% it is correct (it is very hard to tell as the images are nearly identical). I am also thinking that it is possible to pass "imageLocation" at the point where the block is declared.
Can any confirm any of this?
__block int imageLocation = [allImages count] - 1;
// Add another image to MArray
[allImages addObject:[UIImage imageNamed:#"downloading.png"]];
imageLocation = [allImages count] - 1;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
[request setTimeoutInterval: 10.0];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data != nil && error == nil)
{
//All Worked
[allImages replaceObjectAtIndex:imageLocation withObject:[UIImage imageWithData:data]];
}
else
{
// There was an error, alert the user
[allImages replaceObjectAtIndex:imageLocation withObject:[UIImage imageNamed:#"error.png"]];
}];
Dealing with asynchronous methods is a pain ;)
In your case its guaranteed that the completion block will execute on the specified queue. However, you need to ensure that the queue has a max concurrent operations count of 1, otherwise concurrent access to shared resources is not safe. That's a classic race http://en.wikipedia.org/wiki/Race_condition. The max concurrent operations of a NSOperationQueue can be set with a property.
In general, completion handlers may execute on any thread, unless otherwise specified.
Dealing with asynchronous methods gets a lot easier when using a concept called "Promises" http://en.wikipedia.org/wiki/Promise_(programming). Basically, "Promises" represent a result that will be evaluated in the future - nonetheless the promise itself is immediately available. Similar concepts are named "futures" or "deferred".
There is an implementation of a promise in Objective-C on GitHub: RXPromise. When using it you also get safe access from within the handler blocks to shared resources. An implementation would look as follows:
-(RXPromise*) fetchImageFromURL:(NSString*)urlString queue:(NSOperationQueue*) queue
{
#autoreleasepool {
RXPromise* promise = [[RXPromise alloc] init];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
[NSURLConnection sendAsynchronousRequest:request
queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data != nil) {
[promise fulfillWithValue:data];
}
else { // There was an error
[promise rejectWithReason:error];
};
}];
return promise;
}
}
Then call it:
- (void) fetchImages {
...
for (NSUInteger index = 0; index < N; ++index)
{
NSString* urlString = ...
[self fetchImageFromURL:urlString, self.queue]
.then(^id(id data){
[self.allImages replaceObjectAtIndex:index withObject:[UIImage imageWithData:data]];
return #"OK";
},
^id(NSError* error) {
[self.allImages replaceObjectAtIndex:index withObject:[UIImage imageNamed:#"error.png"]];
return error;
});
}
}
A couple of thoughts:
If you want to download 60 images, I would not advise using a serial queue for the download (e.g. do not use an operation queue with maxConcurrentOperationCount of 1), but rather use a concurrent queue. You will want to synchronize the updates to make sure your code is thread-safe (and this is easily done by dispatching the updates to a serial queue, such as the main queue, for that final update of the model), but I wouldn't suggest using a serial queue for the download itself, as that will be much slower.
If you want to use the NSURLConnection convenience methods, I'd suggest something like the following concurrent operation request approach (where, because it's on a background queue, I'm using sendSynchronousRequest rather than sendAsynchronousRequest), where I'll assume you have an NSArray, imageURLs, of NSURL objects for the URLs of your images:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"all done %.1f", CFAbsoluteTimeGetCurrent() - start);
NSLog(#"allImages=%#", self.allImages);
}];
[imageURLs enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] returningResponse:&response error:&error];
if (!data) {
NSLog(#"%s sendSynchronousRequest error: %#", __FUNCTION__, error);
} else {
UIImage *image = [UIImage imageWithData:data];
if (image) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self.allImages replaceObjectAtIndex:idx withObject:image];
});
}
}
}];
[queue addOperation:operation];
[completionOperation addDependency:operation];
}];
[[NSOperationQueue mainQueue] addOperation:completionOperation];
A couple of asides: First, I'm using an operation queue rather than a GCD concurrent queue, because it's important to be able to constrain the degree on concurrency. Second, I've added a completion operation, because I assume it would be useful to know when all the downloads are done, but if you don't need that, the code is obviously simper. Third, that benchmarking code using CFAbsoluteTime is unnecessary, but useful solely for diagnostic purposes if you want to compare the performance using a maxConcurrentOperationCount of 4 versus 1.
Better than using the NSURLConnection convenience methods, above, you might want to use a NSOperation-based network request. You can write your own, or better, use a proven solution, like AFNetworking. That might look like:
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"all done %.1f", CFAbsoluteTimeGetCurrent() - start);
NSLog(#"allImages=%#", self.allImages);
}];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFImageResponseSerializer serializer];
manager.operationQueue.maxConcurrentOperationCount = 4;
[imageURLs enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
NSOperation *operation = [manager GET:[url absoluteString] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.allImages replaceObjectAtIndex:idx withObject:responseObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%s image request error: %#", __FUNCTION__, error);
}];
[completionOperation addDependency:operation];
}];
[[NSOperationQueue mainQueue] addOperation:completionOperation];
Because AFNetworking dispatches those completion blocks back to the main queue, that solves the synchronization issues, while still enjoying the concurrent network requests.
But the main take-home message here is that an NSOperation-based network request (or at least one that uses NSURLConnectionDataDelegatemethods) opens additional opportunities (e.g. you can cancel all of those network requests if you have to, you can get progress updates, etc.).
Frankly, having walked through two solutions that illustrate how to download the images efficiently up-front, I feel compelled to point out that this is an inherently inefficient process. I might suggest a "lazy" image loading process, that requests the images asynchronously as they're needed, in a just-in-time (a.k.a. "lazy") manner. The easiest solution for this is to use a UIImageView category, such as provided by AFNetworking or SDWebImage. (I'd use AFNetworking's if you're using AFNetworking already for other purposes, but I think that SDWebImage's UIImageView category is a little stronger.) These not only seamlessly load the images asynchronously, but offer a host of other advantages such as cacheing, more efficient memory usage, etc. And, it's as simple as:
[imageView setImageWithURL:url placeholder:[UIImage imageNamed:#"placeholder"]];
Just a few thoughts on efficiently performing network requests. I hope that helps.

Resources