The problem is, is the loop runs and causes all of the blocks to trigger, then they are all loading at the same time
That is not a problem, it is good - the reason why the call is asynchronous is it can take an arbitrarily long time to complete. If you've got multiple downloads to do then doing then concurrently can be a big win.
thus the imageArray will result in a different order to the original array of PFFiles, because if one object finishes loading before the previous object, it will be added to the array before the previous one, making it go out of order.
This is the problem and can be addressed in a number of ways.
As you are using arrays, here is a simple array based solution: first create yourself an array of the right size and fill it with nulls to indicate the image hasn't yet arrived:
(all code typed directly into answer, treat as pseudo-code and expect some errors)
NSUInteger numberOfImages = pictureArray.length;
NSMutableArray *downloadedImages = [NSMutableArray arrayWithCapacity:numberOfImages];
// You cannot set a specific element if it is past the end of an array
// so pre-fill the array with nulls
NSUInteger count = numberOfImages;
while (count-- > 0)
[downloadedImages addObject:[NSNull null]];
Now you have your pre-filled array just modify your existing loop to write the downloaded image into the correct index:
for (NSUInteger ix = 0; ix < numberOfImages; ix++)
{
PFFile *picture = pictureArray[ix];
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
dispatch_async(dispatch_get_main_queue(),
^{ [imageArray replaceObjectAtIndex:ix
withObject:[UIImage imageWithData:data]
});
[self savePhotos];
}];
}
The use of dispatch_async
here is to ensure there are not concurrent updates to the array.
If you wish to know when all images have been downloaded you can check for that within the dispatch_async
block, e.g. it can increment a counter safely as it is running on the main thread and call a method/issue a notification/invoke a block when all the images have downloaded.
You are also possibly making things harder on yourself by using arrays, and trying to keep items in different arrays related by position. Dictionaries could save you some of the hassle, for example each of your PFFile
objects presumably relates to a different URL, and a URL is a perfectly valid key for a dictionary. So you could do the following:
NSMutableDictionary *imageDict = [NSMutableDictionary new];
for (PFFile *picture in pictureArray) {
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
dispatch_async(dispatch_get_main_queue(),
^{ [imageDict setObject:[UIImage imageWithData:data] forKey:picture.URL];
};
[self savePhotos];
}];
}
And your can locate the image for a PFFile
instance by looking up its URL in the dictionary - which will return nil
if the image is not yet loaded.
There are other solutions, and you might want to look into making your code more asynchronous. Whatever you do trying to call asynchronous code synchronously is not a good idea and will impact the user experience.
HTH