Synchronization issue with ALAssetsLibrary assetForURL method - ios

I am having some synchronization issue with loading asset from ALAssetsLibrary.
Actually, what I am trying is to load some pictures from camera roll whose urls are given by some database query. Now after obtaining urls from database I use those urls to load the pictures using assetForURL method of ALAssetsLibrary and after the picture is loaded I display the picture to some view. So I call the method inside a loop that is executed every time the query result-set returns a record. And everything works fine till now. Below is a sample code to demonstrate the process:
ALAssetsLibrary* library = [ALAssetsLibrary new];
//dispatch_group_t queueGroup = dispatch_group_create();
while ([rs next]) {
//some data load up
//load thumbnails of available images
[library assetForURL:url resultBlock:^(ALAsset *asset) {
UIImage* img = [[UIImage imageWithCGImage:asset.aspectRatioThumbnail] retain];
//dispatch_group_async(queueGroup, dispatch_get_main_queue(), ^{
//create views and add to container view
CGFloat left = (8.0f + dimension) * i + 8.0f;
CGRect rect = CGRectMake(left, 8.0f, dimension, dimension);
TileView* tileView = [[NSBundle mainBundle] loadNibNamed:#"TileView" owner:nil options:nil][0];
[tileView setFrame:rect];
tileView.tag = i;
tileView.active = NO;
[self.thumbnailContainer addSubview:tileView];
//display image in tileView, etc.
//.............
if (img) {
[img release];
}
NSLog(#"block %d: %d",i,[self.thumbnailContainer.subviews count]);
//});
} failureBlock:^(NSError *error) {
NSLog(#"failed to load image");
}];
i++;
}
NSLog(#"outside block %d",[self.thumbnailContainer.subviews count]);
[library release];
In my code self.thumbnailContainer is a UIScrollView and inside that I add my custom views to display the thumbnail images and it works as expected.
The real dilemma comes when I try to select the very last view added to self.thumbnailContainer. I cant find any way to determine when all the asynchronous blocks of assetForURL methods completed so that self.thumbnailContainer actually contains some subviews. So if I log count of subviews of self.thumbnailContainer just after the loop completes it shows 0. And after that I find all the block codes get executed increasing count of subviews. It is very expected behavior but contradicts my requirements. I have tried dispatch_group_ and dispatch_wait methods from GCD but without any success.
Can anyone please suggest a workaround or an alternative coding pattern to overcome the situation. Any help would be highly appreciated. Thanks.

You might utilize a dispatch group, as you likely had in mind:
- (void) loadViewsWithCompletion:(completion_t)completionHandler {
ALAssetsLibrary* library = [ALAssetsLibrary new];
dispatch_group_t group = dispatch_group_create();
while ([rs next]) {
dispatch_group_enter(group);
[library assetForURL:url resultBlock:^(ALAsset *asset) {
UIImage* img = [[UIImage imageWithCGImage:asset.aspectRatioThumbnail] retain];
dispatch_async(dispatch_get_main_queue(), ^{
//create views and add to container view
...
dispatch_group_leave(group);
});
} failureBlock:^(NSError *error) {
NSLog(#"failed to load image");
dispatch_group_leave(group);
}];
i++;
}
[library release];
if (completionHandler) {
dispatch_group_notify(group, ^{
completionHandler(someResult);
});
}
... release dispatch group if not ARC
}
The code might have a potential issue though:
Since you asynchronously load images, they may be loaded all in parallel. This might consume a lot of system resources. If this is the case, which depends on the implementation of the asset loader method assetForURL:resultBlock:failureBlock:, you need to serialize your loop.
Note: Method assetForURL:resultBlock:failureBlock: may already ensure that access to the asset library is serialized. If the execution context of the completion block is also a serial queue, [UIImage imageWithCGImage:asset.aspectRatioThumbnail] will be executed in serial. In this case, your loop just enqueues a number of tasks - but processes only one image at a time and you are safe.
Otherwise, if method assetForURL:resultBlock:failureBlock: runs in parallel, and/or the block executes an a concurrent queue - images might be loaded and processed in parallel. This can be a bad thing if those images are large.

Two ways you can fix this issue. They are
1.NSLock. Specifically NSConditionLock. Basically you create a lock with the condition “pending tasks”. Then in the completion and error blocks of assetForURL, you unlock it with the “all complete” condition. With this in place, after you call assetForURL, simply call lockWhenCondition using your “all complete” identifier, and you’re done (it will wait until the blocks set that condition)!
Check this for more detials
2.Manually track the count in resultBlock & failureBlock
Note:
I have mentioned few important things regarding the performance of ALAsset libraries in my answer.

Related

Block-wrapping semaphore blocking main thread

I've got an API connector that downloads images asynchronously from an API (basically a smart wrapper around SDWebImageManager). This serves my purpose perfectly, except in one rare instance I need to use the images as in an image slideshow datasource delegate that expects a UIImage to be returned:
- (UIImage*)slideShow:(KASlideShow *)slideShow imageForPosition:(KASlideShowPosition)position
{
return [self randomImageFromConfig];
}
So I'm trying to use a semaphore to wrap my block call so that I can ensure the image is returned before the slideshow datasource method completes:
- (UIImage*)randomImageFromConfig
{
__block UIImage* returnImage;
NSMutableArray* slideShowImages = [[kGlobalRemoteConfig valueForKeyPath:#"images.home"] mutableCopy];
if (slideShowImages && slideShowImages.count > 0)
{
[slideShowImages shuffle];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[ImageDownloader imageForId:slideShowImages[0] collection:#"images" operation:nil completion:^(UIImage *image, NSError *error) {
returnImage = image;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
return returnImage;
}
But this appears to be blocking the main thread? Is this somehow related to my underlying method calling SDWebImage? Or am I just being silly? How can I get this block operation to work? Or is there a better way to go about this datasource?

How to make loop wait until block has finished?

I have an array of PFFiles I retrieve from parse. The PFFiles must be converted to images and I am trying to convert them in a loop, however;
The array of converted images must be in the same order of the array containing the PFFiles.
The problem is, is the loop runs and causes all of the blocks to trigger, then they are all loading at the same time 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.
Instead, I would like a loop where it loops through every object in the array, however it doesn't loop onto the next object until the getDataInBackgroundBlock has finished loading and the current object has been added to the new array.
-(void)organizePhotos {
for (PFFile *picture in pictureArray) {
//This block loads, after the next index in the loop is called, causing the array to be out of order.
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[imageArray addObject:[UIImage imageWithData:data]];
[self savePhotos];
}];
}
}
Above is my code. Is there anyway I can make the loop wait until the getDatanBackgroundWithBlock finishes loading?
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
Ideally, you should use a synchronous method; however the behavior you are asking for can be achieved using Grand Central Dispatch.
First, create a dispatch_group_t using dispatch_group_create(). Let's call it asyncGroup.
Then, before calling the async method, call dispatch_group_enter(asyncGroup). This increments the counter of the number of calls in the group.
Then, at the end of the async block, call dispatch_group_leave(asyncGroup) to decrement the counter of the number of calls in the group.
Finally, after calling the async method, call dispatch_group_wait(asyncGroup, timeout) to pause thread execution until the group counter reaches zero. Since you increment, make the call, and then wait, the loop will only continue when the async block has been run. Just ensure the timeout is longer than the operation will take.
You can make the code synchronous:
-(void)organizePhotos {
for (PFFile *picture in pictureArray) {
[imageArray addObject:[UIImage imageWithData:[picture getData]]];
[self savePhotos];
}
}
and run it in the background:
[self performSelectorInBackground:#selector(organizePhotos) withObject:nil];
You can use GCD approach which is to use dispatch_group. So, before you start an asynchronous task, call dispatch_group_enter, and then when the asynchronous task finishes, call dispatch_group_leave, and you can then create a dispatch_group_notify which will be called when the asynchronous tasks finish. You can marry this with a completion block pattern (which is a good idea for asynchronous methods, anyway):
Here is similar Question. Perhaps it may also be helpful:
How to wait for method that has completion block (all on main thread)?
You could do that using dispatch_async way, using semaphory, groups, but for your need better practice is using block to get response and do what you need.
Doing that:
-(void)organizePhotos:(void(^)(BOOL responseStatus))status {
for (PFFile *picture in pictureArray) {
//This block loads, after the next index in the loop is called, causing the array to be out of order.
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[imageArray addObject:[UIImage imageWithData:data]];
status(YES);
}];
}
}
So you next at your call you could do that:
__weak typeof(self) weakSelf = self;
[self organizePhotos:^(BOOL responseStatus) {
if(responseStatus){
[weakSelf savePhotos];
}
}];
Or if you dont wan't create extra parameter response to your method you could do like this:
-(void)organizePhotos {
__weak typeof(self) weakSelf = self;
void (^ResponseBlock)(void) = ^{
[weakSelf savePhotos];
};
for (PFFile *picture in pictureArray) {
//This block loads, after the next index in the loop is called, causing the array to be out of order.
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[imageArray addObject:[UIImage imageWithData:data]];
ResponseBlock();
}];
}
}
And another point that you are making wrong when calling "self" inside block, this could lead you to retain cycle and its a bad practice, look at my code how you should do.

Adding pictures from array containing url's to imageView of cell - iOS Xcode Obj C

Just to better give an idea of what this post is about, it ultimately ends out in this question:
How do I asynchronously download a not predefined number of images from any number of url's, add them to a dictionary (or array, if that's easier) so that they are added in the order the download is started, instead of adding them in the order they are finished?
This is the backbone question of this post, however for good measures and to actually allow one to understand what I mean, I have added my specific case involving this question in the following post. I know it's long, but it's pretty complex to explain my case.
So, here goes nothing:
I have a tableView which loads a couple of things from different arrays. All the arrays is created from a JSON fetch each time the app is launched, the idea being that I can update the info in my app by simply updating the JSON text file. One of the entries in the JSON text file contain url's to images, which I want to add to the tableView cell's contentview. I got the JSON fetch working, and a dictionary "dataDictionary" containing the info from the fetch is created.
The arrays then, get created like this:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// Array is created from the dictionary created from the JSON fetch
_dataArray = _dataDictionary[#"games"];
// Arrays that will eventually hold info from JSON file is created
_Titles = [[NSMutableArray alloc] init];
_Descriptions = [[NSMutableArray alloc] init];
_Date = [[NSMutableArray alloc] init];
//images will be added to dictionary instead
_imageDict = [[NSMutableDictionary alloc]init];
// Info parsed from the JSON fetch, is now added to arrays
for (NSDictionary *eachData in _dataArray)
{
_Title = eachData[#"Title"];
_Description = eachData[#"Description"];
_Date = eachData[#"Release"];
// Key for image url is created
_image = eachData[#"ImageLink"];
[_Titles addObject:_Title];
[_Descriptions addObject:_Description];
[_Dates addObject:_Date];
Now there is more code below this, which is where I handle the images (will come just after this short explanation and below code sample), as you can see I have specified a key named "image" for the ImageLink entry in the JSON. Now I call this method, which starts an asynchronous download of the images:
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^) (BOOL succeeded, UIImage *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
} else{
completionBlock(NO,nil);
}
}];
}
This method is called in the below code, so to continue the code from before:
// Just to be clear, we are now back in the "for (NSDictionary *eachData in _dataArray)" statement from the first code sample
//calling the above method here
[self downloadImageWithURL:[NSURL URLWithString:_image] completionBlock:^(BOOL succeeded, UIImage *image) {
if (succeeded) {
//All this code runs when an image is successfully downloaded
NSLog(#"image download succesfull");
UIImage *downloadedImage = [[UIImage alloc] init];
// Doing this so I can resize the downloaded image to a proper size
downloadedImage = image;
//long boring code to resize image here
UIImage *resizedImage = [[UIImage alloc] init];
// resizedImage is as the name implies, the resized image that now has a proper size for the cell's content view
_number++; //Int that start out as -1, this increments each time an image is downloaded to allow the images to be added to the dictionary with the keys 0, 1, 2...
// Wrapping the int in a NSNumber
NSNumber *number = [NSNumber numberWithInt:_number];
// Turning the NSnumber into a string
NSString *key = [number stringValue];
// Images finally being added to the dictionary, but, in the wrong "order" - or rather, with the wrong keys / numbers
[_imageDict setObject:resizedImage forKey:key];
//Update tableView when images are added to dictionary
if (_imageDict.count == _gameTitles.count) {
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
}
}
}];
//Update tableView when data is added to array, this is to allow info to be shown even before the images are done downloading
if (Titles.count == _dataArray.count) {
// Update tableView with loaded content
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
}
} // "for" statement ends here
}
To sum the most important parts up:
When filling the cell's in the tableView, I use the indexPath.row to get the proper info from each array.
The images download, and I add them to an dictionary with the keys 0, 1, 2.. when finished, however, because the download is asynchronous the download do not finish in the order it was initialized, rather, the smaller images get (not surprisingly) downloaded and added to the image dictionary first. This means that the keys of the images does not fit with the order in all my other arrays, so setting the image in the first cell with indexPath.row simply sets the image that was downloaded first, rather than the image download that was started first. Basically, even if image 5 gets downloaded first, it should be added with the key "4", and not "0".
I should also mention that because of the JSON fetch I do NOT know how many pictures will be downloaded beforehand as this can change depending on the JSON. This cancels out a lot of the answers here on stackoverflow (and other places as well).
So all this text and code and whatnot leads to the beginning question, how do I asynchronously download a not predefined number of images from any number of url's, add them to a dictionary (or array, if that's easier) so that they are added in the order the download is started, instead of adding them in the order they are finished?
Thank you very much for taking your time to read this.
So all this text and code and whatnot leads to the beginning question, how do I asynchronously download a not predefined number of images from any number of url's, add them to a dictionary (or array, if that's easier) so that they are added in the order the download is started, instead of adding them in the order they are finished?
You can use SDWebImage Library to download images,it asynchronously downloads your images from given URLs and it also caches images and manages all the things for you and it is really popular.All you have to do is to add below code to your -cellForRowAtIndexPath delegate method:
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:imageURLThatYouGetFromJSONResponse]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Also I recommend you not to use arrays or dictionaries.You can store that all information in an object.It is easy and best-practise in many cases and called as object-oriented approach.You create an NSObject subclass and in there create properties "title", "description", "name", "imageURL".You can follow this OOP Tutorial for some better understanding.
You don't care about number of images, because you define the number of rows in -numberOfRowsInSection and -cellForRowAtIndexPath is called the times you wrote in -numberOfRowsInSection.You can add:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [arrayOfYourObjects count];
}
Edit: How to manipulate images while using SDWebImage?
Answer: You can use SDWebImageManager, it also manages caching for you
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// Code to resize
//After resizing
cell.imageView.image = resizedImage;
}
}];

ios programming: Using threads to add multiple images to library

In Xcode, when I'm trying to add more than 5 images to my library, it gives me the following error:
Error Domain=ALAssetsLibraryErrorDomain Code=-3301 "Write busy" UserInfo=0xa706aa0 {NSLocalizedRecoverySuggestion=Try to write again, NSLocalizedFailureReason=There was a problem writing this asset because the writing resources are busy., NSLocalizedDescription=Write busy, NSUnderlyingError=0xa770110 "Write busy"}
In order to solve this problem, I figured out threads would solve my problems. The documentation states that I can use POSIX threads or NSThreads. When I try using POSIX threads, I set my threads to be joinable, and I'm creating a void * function:
void * myFunc (void * image)
{
UIImageWriteToSavedPhotosAlbum((__bridge UIImage *)(image),self,nil,nil);
pthread_exit(NULL);
return NULL;
}
I am also waiting for the thread to end. But still only 5 images are written.
I've tried using NSThreads and did:
[self performSelectorOnMainThread:#selector(myFunc:) withObject:image waitUntilDone:YES];
But still it doesn't work.
Is there an answer to my problem? It's crucial to my work.
Thanks.
Edit:
Tried dispatch_async too. Is it wrong?
dispatch_queue_t myQueue = dispatch_queue_create("com.cropr.myqueue", 0);
for (UIImage * image in images) {
dispatch_async(myQueue, ^{
[self.library saveImage:image toAlbum:#"Cropr" withCompletionBlock:^(NSError *error) {
if (error!=nil) {
NSLog(#"Big error: %#", [error description]);
}
}];
});
}
What do I need to add?
You may try to write all your images subsequently, instead of simultaneously. The following code utilizes ALAssetsLibrary, and implements an "asynchronous loop" which invokes a number of asynchronous methods in sequence.
typedef void (^completion_t)(id result);
- (void) writeImages:(NSMutableArray*)images
completion:(completion_t)completionHandler {
if ([images count] == 0) {
if (completionHandler) {
// Signal completion to the call-site. Use an appropriate result,
// instead of #"finished" possibly pass an array of URLs and NSErrors
// generated below in "handle URL or error".
completionHandler(#"finished");
}
return;
}
UIImage* image = [images firstObject];
[images removeObjectAtIndex:0];
[self.assetsLibrary writeImageToSavedPhotosAlbum:image.CGImage
orientation:ALAssetOrientationUp
completionBlock:^(NSURL *assetURL, NSError *error)
{
// Caution: check the execution context - it may be any thread,
// possibly use dispatch_async to dispatch to the main thread or
// any other queue.
// handle URL or error
...
// next image:
[self writeImages:images completion:completionHandler];
}];
}
Usage:
[foo writeImages:[foo.images mutableCopy] completion:^(id result){
// Caution: check the execution context - it may be any thread
NSLog(#"Result: %#", result);
}];
I'd recommend using an NSOperationQueue and play with the value of maxConcurrentOperationCount. This way you can control the number of simultaneous writes to the library, and not overwhelm it.
If you use threads, or even GCD, you'd need to implement this logic yourself. More code --> more chance of introducing a bug.
Dispatch_async is something to do in background , so I put your for{} and function call inside the dispatch_async body
so putting for inside the dispatch async will store your imaged in the album,like just running for. but on seperate thread.
dispatch_queue_t myQueue = dispatch_queue_create("com.cropr.myqueue", 0);
dispatch_async(myQueue, ^{
for (UIImage * image in images)
UIImageWriteToSavedPhotosAlbum((__bridge UIImage *)(image),self,nil,nil);
});
}
You can also try this. i think will be better, watch if it works , then add what you want to handle the errors.
Also i think here is your desired answer : iOS: dispatch_async and UIImageWriteToSavedPhotosAlbum

IOS: Search algorithm with proper GCD code

I made my search algorithm for my UISearchBar, and I know that I must search in a background thread. Honestly I'm not familiar with multi-threading, so I'm looking for help. I'm using GCD (Grand Central Dispatch).
Here is my code, I want to know is it correct or not.
-(void)mySearchMethod
{
NSArray *allObjects = self.allMyObjects;
__block NSMutableArray *searchArray = [[NSMutableArray alloc] init];
dispatch_queue_t queue;
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async( queue, ^{
for (MyObjectClass *myObjectAsDictionary in allObjects) {
for (NSString *titleSubString in [myObjectAsDictionary.title componentsSeparatedByString:#" "]) {
if ([[titleSubString lowercaseString] hasPrefix:[text lowercaseString]]) {
[searchArray addObject: myObjectAsDictionary];
}
}
}
dispatch_async( dispatch_get_main_queue(), ^{
self.tableObjects = searchArray;
[self.myTableView reloadData];
});
});
}
So does this code work in the background, or is it blocking the main thread?
Can the contents of allMyObjects change while the search is running? If so, you have two problems. First problem: you should not accessing an array on one thread while it's being mutated on another. You can fix this simply by copying the array: NSArray *allObjects = [self.allMyObjects copy];. Second problem: your search results might contain objects that have been removed from allMyObjects, or might be missing objects that have been added to allMyObjects. This is a hard problem to solve and how you solve it depends on your app.
What happens if the user cancels the search before your async block finishes running? Will it replace the contents of a table with search results even though the user doesn't want to see the results now?

Resources