ALAssetsLibrary enumerateGroupsWithTypes: - Thread synchronization - iphone

I'm using [ALAssetsLibrary enumerateGroupsWithTypes:] to store the ALAssets in an array. As this is an asynchronous operation, I need to wait for it to finish before continuing my work.
I read Cocoa thread synchronisation when using [ALAssetsLibrary enumerateGroupsWithTypes:] and tried the recommended NSConditionLock. However, the blocks are always performed in the main thread, thus if I wait using the conditionlock, the main thread is blocked and the blocks won't get executed -> I'm stuck.
I even tried running the method loadAssets on a new thread, but still the blocks get executed on the main thread.
I can't find a way to actually wait for the enumeration to finish. Is there a way to force the blocks to a different thread than the main thread or anything else I can do?
Here's the code:
- (void)loadAssets
{
assets = [NSMutableArray array];
NSConditionLock *threadLock = [[NSConditionLock alloc] initWithCondition:THREADRUNNING];
void (^assetEnumerator)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if(result != nil)
{
[assets addObject:result];
}
};
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop)
{
if(group != nil)
{
[group enumerateAssetsUsingBlock:assetEnumerator];
}
[threadLock lock];
[threadLock unlockWithCondition:THREADFINISHED];
};
void (^assetFailureBlock)(NSError *) = ^(NSError *error)
{
[threadLock lock];
[threadLock unlockWithCondition:THREADFINISHED];
};
ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];
[assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:assetGroupEnumerator failureBlock:assetFailureBlock];
[threadLock lockWhenCondition:THREADFINISHED];
[threadLock unlock];
[assetsLibrary release];
[threadLock release];
}

you are killing your assetsLibrary object before it finished to enumrate the objects.
move it to your .h file and release it in dealloc.

I know this thread is probably dead, but I'm answering it because this is where I landed by Googling
The trick is to lock your background thread until the enumeration yields a nil-group. This category is one example of how to use it, you can use the category method as a drop-in replacement of the ALAssetsLibrary enumeration method.
#implementation ALAssetsLibrary (EEEConcurrency)
- (NSUInteger)eee_enumerateGroupsLockedWithTypes:(ALAssetsGroupType)types
usingBlock:(ALAssetsLibraryGroupsEnumerationResultsBlock)enumerationBlock
failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
{
NSAssert(![NSThread isMainThread], #"This would create a deadlock (main thread waiting for main thread to complete)");
enum
{
EEEAssetsLibraryDone,
EEEAssetsLibraryBusy
};
NSConditionLock *assetsLibraryConditionLock = [[NSConditionLock alloc] initWithCondition:EEEAssetsLibraryBusy];
__block NSUInteger numberOfGroups = 0;
[self enumerateGroupsWithTypes:types
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
enumerationBlock(group, stop);
if (group) numberOfGroups++;
if (!group || *stop)
{
[assetsLibraryConditionLock lock];
[assetsLibraryConditionLock unlockWithCondition:EEEAssetsLibraryDone];
}
}
failureBlock:^(NSError *error) {
failureBlock(error);
[assetsLibraryConditionLock lock];
[assetsLibraryConditionLock unlockWithCondition:EEEAssetsLibraryDone];
}];
[assetsLibraryConditionLock lockWhenCondition:EEEAssetsLibraryDone];
[assetsLibraryConditionLock unlock];
return numberOfGroups;
}
#end
Download the category at https://gist.github.com/epologee/8890692

This code chunk should work for you -->
ALAssetsLibrary *assetsLib = [[ALAssetsLibrary alloc] init];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block int numberOfGroups = 0;
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[assetsLib enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group) {
numberOfGroups++;
}
else {
dispatch_semaphore_signal(sema);
}
} failureBlock:^(NSError *error) {
NSLog(#"enumerateGroupsWithTypes failure %#", [error localizedDescription]);
dispatch_semaphore_signal(sema);
}];
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
NSLog( #"number of groups is: %d", numGroups);
Although instead of DISPATCH_TIME_FOREVER you may want to make the time one second from now

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_assetsLibrary = [[ALAssetsLibrary alloc] init];
ALAssetsLibraryGroupsEnumerationResultsBlock listGroupBlock = ^(ALAssetsGroup *group, BOOL *stop) {
if (group)
{
if (((NSString *)[group valueForProperty:ALAssetsGroupPropertyType]).intValue == ALAssetsGroupAlbum)
{
[albumSettingsList addObject:group];
} else {
dispatch_semaphore_signal(sema);
}
}
};
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError *error) {
switch ([error code]) {
case ALAssetsLibraryAccessUserDeniedError:
case ALAssetsLibraryAccessGloballyDeniedError:
break;
default:
break;
}
dispatch_semaphore_signal(sema);
};
NSUInteger groupTypes = ALAssetsGroupAlbum | ALAssetsGroupEvent | ALAssetsGroupFaces | ALAssetsGroupSavedPhotos;
[self.assetsLibrary enumerateGroupsWithTypes:groupTypes usingBlock:listGroupBlock failureBlock:failureBlock];
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
whenever you want to get album list synchoronos with IOS 7

Use dispatch_semaphore_t to control it
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:^(ALAssetsGroup *assetsGroup, BOOL *stop) {
if (assetsGroup) {
// Filter the assets group
[assetsGroup setAssetsFilter:[ALAssetsFilter allAssets]];
// Add assets group
if (assetsGroup.numberOfAssets > 0) {
// Add assets group
DNAlbum *album = [DNAlbum albumWithAssetGroup:assetsGroup];
[albumArray addObject:album];
}
}
if (*stop || !assetsGroup) {
dispatch_semaphore_signal(sem);
}
} failureBlock:^(NSError *error) {
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

Related

ALAssetsLibrary is too slow - Objective-C

What is a fast way to load 10-20 fullscreen images from a camera roll, saved photos?
I'm using this code, but to load 10 photos I need to wait about 5-10 seconds. I'm using iPhone 4S.
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if(_savedPhotos.count>=11) *stop = YES;
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *needToStop) {
NSLog(#"%d",index);
if(_savedPhotos.count<11)
{
UIImage *image = [UIImage imageWithCGImage:result.defaultRepresentation.fullScreenImage];
[_savedPhotos addObject:image];
}
else
{
*needToStop = YES;
}
}];
} failureBlock:^(NSError *error) {
NSLog(#"%#",error.description);
}];
The ALAssetsLibrary library will run on a separate thread. So it may take time to communicate with the UI related and other stuff.
So use -performSelectorOnMainThread:withObject:waitUntilDone: inside the ALAssetsLibrary block.
Change your code as below
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *needToStop) {
NSLog(#"%d",index);
UIImage *image = [UIImage imageWithCGImage:result.defaultRepresentation.fullScreenImage];
[self performSelectorOnMainThread:#selector(usePhotolibraryimage:) withObject:image waitUntilDone:NO];
}];
}
failureBlock:^(NSError *error) {
NSLog(#"%#",error.description);
}];
- (void)usePhotolibraryimage:(UiImage *)myImage{
//Do your all UI related and all stuff here
}
Note:Look on this issue too.

how to get total count of photos from iphone photoLibrary.?

i want to get the count of photos in photoLibrary. Currently, i'am able to get the photoes from photoLibrary and add to myApp's Document directry ONE BY ONE. But what i want is, save all the photos from photoLibrary to Document directry of myApp ALL AT ONCE. Thats why i need the count of photoes in photoLibrary. I've used UIImagePickerControllerSourceTypePhotoLibrary to retrieve the photoes from iPhone photoLibrary.
Any help would be appreaciated..
thanks in advance....
Use ALAssetsLibrary for this:
int imgCount = 0;
self.assetsLibrary = [[ALAssetsLibrary alloc] init];
dispatch_queue_t dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(dispatchQueue, ^(void) {
[self.assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
[group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index,
__block BOOL foundThePhoto = NO;
if (foundThePhoto){ *stop = YES;
}
BOOL *stop) {
/* Get the asset type */
NSString *assetType = [result valueForProperty:ALAssetPropertyType];
if ([assetType isEqualToString:ALAssetTypePhoto]){ NSLog(#"This is a photo asset");
foundThePhoto = YES; *stop = YES;
/* Get the asset's representation object */
ALAssetRepresentation *assetRepresentation = [result defaultRepresentation];
/* We need the scale and orientation to be able to construct a properly oriented and scaled UIImage out of the representation object */
CGFloat imageScale = [assetRepresentation scale];
UIImageOrientation imageOrientation = (UIImageOrientation)[assetRepresentation orientation];
dispatch_async(dispatch_get_main_queue(), ^(void) {
CGImageRef imageReference = [assetRepresentation fullResolutionImage];
/* Construct the image now */
UIImage *image = [[UIImage alloc] initWithCGImage:imageReference
scale:imageScale orientation:imageOrientation];
//Write image to doument directory
imgCount ++;
}
});
} failureBlock:^(NSError *error) {
} }];
NSLog(#"Failed to enumerate the asset groups."); }];
})
NSLog(#"Total Image Count %d",imgCount);
The code block below counts all videos and photos:
__block int videoCount = 0;
__block int photoCount = 0;
ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc]init];
[assetLibrary
enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group == nil) {
// enumeration complete
return;
}
int total = group.numberOfAssets;
[group setAssetsFilter:[ALAssetsFilter allVideos]];
int groupVideoCount = group.numberOfAssets;
videoCount += groupVideoCount;
photoCount += total - groupVideoCount;
}
failureBlock:^(NSError *error) {
// Handle error
}];

How to check if an ALAsset still exists using a URL

I've got an array containing ALAsset urls (not full ALAsset objects)
So each time I start my application I want to check my array to see if it's still up to date...
So I tried
NSData *assetData = [[NSData alloc] initWithContentsOfFile:#"assets-library://asset/asset.PNG?id=1000000001&ext=PNG"];
but assetData is allways nil
thx for the help
Use assetForURL:resultBlock:failureBlock: method of ALAssetsLibrary instead to get the asset from its URL.
// Create assets library
ALAssetsLibrary *library = [[[ALAssetsLibrary alloc] init] autorelease];
// Try to load asset at mediaURL
[library assetForURL:mediaURL resultBlock:^(ALAsset *asset) {
// If asset exists
if (asset) {
// Type your code here for successful
} else {
// Type your code here for not existing asset
}
} failureBlock:^(NSError *error) {
// Type your code here for failure (when user doesn't allow location in your app)
}];
Having assets path you can use this function to check if image exist:
-(BOOL) imageExistAtPath:(NSString *)assetsPath
{
__block BOOL imageExist = NO;
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:[NSURL URLWithString:assetsPath] resultBlock:^(ALAsset *asset) {
if (asset) {
imageExist = YES;
}
} failureBlock:^(NSError *error) {
NSLog(#"Error %#", error);
}];
return imageExist;
}
Remember that checking if image exist is checking asynchronyus.
If you want to wait until new thread finish his life call function "imageExistAtPath" in main thread:
dispatch_async(dispatch_get_main_queue(), ^{
[self imageExistAtPath:assetPath];
});
Or you can use semaphores, but this is not very nice solution:
-(BOOL) imageExistAtPath:(NSString *)assetsPath
{
__block BOOL imageExist = YES;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(queue, ^{
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:[NSURL URLWithString:assetsPath] resultBlock:^(ALAsset *asset) {
if (asset) {
dispatch_semaphore_signal(semaphore);
} else {
imageExist = NO;
dispatch_semaphore_signal(semaphore);
}
} failureBlock:^(NSError *error) {
NSLog(#"Error %#", error);
}];
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return imageExist;
}
For iOS 8 or later, there is a synchronous method to check if an ALAsset exists.
#import Photos;
if ([PHAsset fetchAssetsWithALAssetURLs:#[assetURL] options:nil].count) {
// exist
}
Swift:
import Photos
if PHAsset.fetchAssetsWithALAssetURLs([assetURL], options: nil).count > 0 {
// exist
}
Swift 3:
import Photos
if PHAsset.fetchAssets(withALAssetURLs: [assetURL], options: nil).count > 0 {
// exist
}
Use this method to check if file exists
NSURL *yourFile = [[self applicationDocumentsDirectory]URLByAppendingPathComponent:#"YourFileHere.txt"];
if ([[NSFileManager defaultManager]fileExistsAtPath:storeFile.path
isDirectory:NO]) {
NSLog(#"The file DOES exist");
} else {
NSLog(#"The file does NOT exist");
}

Wait for assetForURL blocks to be completed

I would like to wait this code to be executed before to continue but as these blocks are called assynchronously I don't know how to do???
NSURL *asseturl;
NSMutableArray *tmpListAsset = [[NSMutableArray alloc] init];
ALAssetsLibrary *library = [[[ALAssetsLibrary alloc] init] autorelease];
NSMutableArray *objectsToRemove = [[NSMutableArray alloc] init];
for (NSDictionary *dico in assetsList) {
asseturl = [NSURL URLWithString:[dico objectForKey:#"assetUrl"]];
NSLog(#"asset url %#", asseturl);
// Try to load asset at mediaURL
[library assetForURL:asseturl resultBlock:^(ALAsset *asset) {
// If asset doesn't exists
if (!asset){
[objectsToRemove addObject:dico];
}else{
[tmpListAsset addObject:[asseturl absoluteString]];
NSLog(#"tmpListAsset : %#", tmpListAsset);
}
} failureBlock:^(NSError *error) {
// Type your code here for failure (when user doesn't allow location in your app)
}];
}
GCD semaphore approach:
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
for (NSURL *url in self.assetUrls) {
dispatch_async(queue, ^{
[library assetForURL:url resultBlock:^(ALAsset *asset) {
[self.assets addObject:asset];
dispatch_semaphore_signal(sema);
} failureBlock:^(NSError *error) {
dispatch_semaphore_signal(sema);
}];
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
dispatch_release(sema);
/* Check out ALAssets */
NSLog(#"%#", self.assets);
The solution is here
http://omegadelta.net/2011/05/10/how-to-wait-for-ios-methods-with-completion-blocks-to-finish/
Note that assetForURL:resultBlock:failureBlock: will stuck if the main thread is waiting without RunLoop running. This is alternative ( cleaner :-) ) solution:
#import <libkern/OSAtomic.h>
...
ALAssetsLibrary *library;
NSMutableArray *assets;
...
__block int32_t counter = 0;
for (NSURL *url in urls) {
OSAtomicIncrement32(&counter);
[library assetForURL:url resultBlock:^(ALAsset *asset) {
if (asset)
[assets addObject:asset];
OSAtomicDecrement32(&counter);
} failureBlock:^(NSError *error) {
OSAtomicDecrement32(&counter);
}];
}
while (counter > 0) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
The easiest thing to do is to move your code to inside (at the end of) the resultBlock or the failureBlock. That way, your code will run in the correct order, and you will also retain asynchronous behaviour.
This is an easy way to do it. Maybe not as elegant as using GCD but it should get the job done ... This will make your method blocking instead of non-blocking.
__block BOOL isFinished = NO;
NSURL *asseturl;
NSMutableArray *tmpListAsset = [[NSMutableArray alloc] init];
ALAssetsLibrary *library = [[[ALAssetsLibrary alloc] init];
NSMutableArray *objectsToRemove = [[NSMutableArray alloc] init];
for (NSDictionary *dico in assetsList) {
asseturl = [NSURL URLWithString:[dico objectForKey:#"assetUrl"]];
NSLog(#"asset url %#", asseturl);
// Try to load asset at mediaURL
[library assetForURL:asseturl resultBlock:^(ALAsset *asset) {
// If asset doesn't exists
if (!asset){
[objectsToRemove addObject:dico];
}else{
[tmpListAsset addObject:[asseturl absoluteString]];
NSLog(#"tmpListAsset : %#", tmpListAsset);
}
if (objectsToRemove.count + tmpListAsset.count == assetsList.count) {
isFinished = YES;
}
} failureBlock:^(NSError *error) {
// Type your code here for failure (when user doesn't allow location in your app)
isFinished = YES;
}];
}
while (!isFinished) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01f]];
}

iphone custom photo album view in inside of the appication(thumbnail)

is it possible to implement the photolibrary view to the custom thumbnail view as the default one is the grid list..(ie.,for example listing it in a uitableview)can u please help me out..i was strucked with this one from past one week....
Read about ALAssetsLibrary
here is my code:
-(void)viewDidLoad{
void (^assetEnumerator)(struct ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *result, NSUInteger index, BOOL *stop) {
if(result != NULL) {
//Add result to your array for UITableView
}
};
void (^assetGroupEnumerator)(struct ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) {
if(group != nil) {
[group enumerateAssetsUsingBlock:assetEnumerator];
}
};
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes: ALAssetsGroupSavedPhotos
usingBlock: assetGroupEnumerator
failureBlock: ^(NSError *error) {
NSLog(#"Failure: %#", error);
}];
}