Can't add ALAssets to NSOperationQueue from enumeration block - ios

I'm trying to enumerate through the ALAssetLibrary to retrieve all saved photos. From the enumeration block, I'm trying to to send each ALAsset for asynchronous processing by passing it to an NSInvocationOperation object, then adding that to an NSOperationQueue. However, only the first ALAsset object is properly passed to the processing method. All subsequent assets are just passed as nil.
Here is my code:
ViewDidLoad:
queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop)
{
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
[group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if (! result) {
return;
}
NSInvocationOperation* operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(processAsset:) object:result];
[queue addOperation:operation];
return;
}];
} failureBlock:^(NSError *error) {
NSLog(#"%#", error);
}];
And the -processAsset method
- (void)processAsset:(ALAsset *)asset
{
NSLog(#"Asset: %#", asset);
// Asset is nil after the first iteration
}
Any help would be really appreciated, thanks!

All ALAssets objects are deallocated when the ALAssetsLibrary that obtained them goes out of scope. If you want to do what you're doing, you'll need to keep a strong reference to library, and then deallocate library when you're done with it.
(In your case, they deallocate at the end of viewDidLoad.)

Related

Need assistance regarding ALAssetsLibrary enumeration

[aLib enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:assetsGroupEnumerationBlock failureBlock:failureBLock];
This method enumerate every group, I want to enumerate for first group only then I want to break it. My purpose is to ask for permission that iOS pop for first time. I am doing no additional work, I have notifications in blocks that notify and trigger other required functionality. But multiple group enumeration trigger notification multiple times and that I want to stop.
Here is my enumeration block with stop parameter
void(^assetsGroupEnumerationBlock)(ALAssetsGroup*, BOOL*) = ^(ALAssetsGroup *groups, BOOL *stop) {
*stop = YES;
NSDictionary *alAuthDict = #{#"alAssetsAuthStatusDictKey" : [NSString stringWithFormat:#"%ld",[self getALAssetAuthorizationStatus]]};
[[NSNotificationCenter defaultCenter]postNotificationName:#"alAssetsStatusNotificationName" object:nil userInfo:alAuthDict];
};
But notification is getting called two times I see nslog twice in console.
Use the stop parameter:
[lib enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
*stop = YES;
if (group) {
NSDictionary *alAuthDict = #{#"alAssetsAuthStatusDictKey" : [NSString stringWithFormat:#"%ld",[self getALAssetAuthorizationStatus]]};
[[NSNotificationCenter defaultCenter]postNotificationName:#"alAssetsStatusNotificationName" object:nil userInfo:alAuthDict];
}
} failureBlock:^(NSError *error) {
// denied
}];

Returning NSArray of UIImages from ALAssetsLibrary

I'm working on photo app and here is the deal:
I have created custom photo album an added some photos to it (users can create many albums). I used ALAssetsLibrary for this and CustomPhotoAlbum category. Everything worked flawlessly until I needed to get those photos back to reuse them.
So Ive created custom metod for this category and I'm saving UIImages to NSMutableArray but I want this method to return this array outside... My problem is that enumerateAssetsUsingBlock: is running in background and saving photos to album while I want to access them! How can I do something for this method to wait until every photo is loaded to album and then return NSArray?
Here is my method:
- (void)loadImagesFromAlbumNamed:(NSString *)name
{
NSMutableArray *photos = [[NSMutableArray alloc] init];
[self enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group == nil) {
return;
}
if ([[group valueForProperty:ALAssetsGroupPropertyName] isEqualToString:name]) {
[group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result == nil) {
return;
}
// NSLog(#"%#", result);
UIImage *image = [UIImage imageWithCGImage:(__bridge CGImageRef)([result defaultRepresentation])];
[photos addObject:image];
}];
NSLog(#"%#", [photos description]);
}
} failureBlock:^(NSError *error) {
NSLog(#"%#", [error localizedDescription]);
}];
}
I hope you get my point of view... If not, just ask and I'll try to explain it better way!
Don't try to wait. Instead, pass a block to your method which should be called when all of the asset processing blocks are completed and will pass the array of images back to the caller. The caller should not block, you just want to structure your method to deal with the asynchronous nature of what you're trying to do.

ALAssetPrivate past the lifetime of its owning ALAssetsLibrary

I am trying to get all the images that are in the photos.app and display them in a UICollectionView. I have this code to retrieve the images:
ALAssetsLibrary *al = [ViewController defaultAssetsLibrary];
ALAssetsGroupEnumerationResultsBlock groupEnumerAtion = ^(ALAsset *result, NSUInteger index, BOOL *stop){
if (result!=NULL) {
if ([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) {
[imagesGotFromUserLibrary addObject:result];
}
}
};
ALAssetsLibraryGroupsEnumerationResultsBlock
libraryGroupsEnumeration = ^(ALAssetsGroup* group, BOOL* stop){
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
if (group!=nil)
{
[group enumerateAssetsUsingBlock:groupEnumerAtion];
}
else
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
GalleryCollectionViewController *otherController = [[GalleryCollectionViewController alloc] init];
[otherController receiveImagesWithMutableArray:imagesGotFromUserLibrary];
});
}
};
al = [[ALAssetsLibrary alloc] init];
[al enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
usingBlock:libraryGroupsEnumeration
failureBlock:^(NSError *error){
NSLog(#"ERROR: %#", error);
}];
This is in the viewDidLoad and then:
+ (ALAssetsLibrary *)defaultAssetsLibrary {
static dispatch_once_t pred = 0;
static ALAssetsLibrary *library = nil;
dispatch_once(&pred, ^{
library = [[ALAssetsLibrary alloc] init];
});
return library;
}
This piece of code is sending an array to another controller that will set the images to my UICollectionView. The problem is that I am getting an error "invalid attempt to access past the lifetime of its owning ALAssetsLibrary" and if I try to NSLog my array the result is something like "ALAsset - Type:Unknown, URLs:(null)".
I looked up on the internet and I found a solution. I should add this line of code but it doesn't work. The code is:
+ (ALAssetsLibrary *)defaultAssetsLibrary {
static dispatch_once_t pred = 0;
static ALAssetsLibrary *library = nil;
dispatch_once(&pred, ^{
library = [[ALAssetsLibrary alloc] init];
});
return library;
}
Anyone is able to help me on getting the correct images URLs to display?
The solution is to always use the same library for all accesses to the assets across all of your classes. The singleton solution you show above is good - so long as all of your classes call defaultAssetsLibrary and none of them alloc/init their own ALAssetsLibrary.
I have the same problem. And I finally solved it.
This is my condition: I have two controllers, the first one push to the second. In the first one I initialized library using following code:
let library = ALAssetsLibrary()
and I put all group information into an array, so that I can use them in the second view controller. But this may lead to no photo in group.
and the error message is the same as yours.
This is because the library is released by system. And all photo information is only valid in the lifetime of its owning ALAssetsLibrary. So the group.numberOfAssets() will be 0.
The solution is avoid library be released before the first controller release.
So I use the following code:
static let library = ALAssetsLibrary()
firstViewController.library.enmurate.....balabala
Hope this may help someone.

How to use ALAssetLibrary to get Local Photos in Camera Roll

ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
usingBlock:libraryGroupsEnumeration
failureBlock:failureblock];
ALAssetsGroupEnumerationResultsBlock groupEnumerAtion = ^(ALAsset *result, NSUInteger index, BOOL *stop){
if (result!=NULL) {
if ([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) {
[self._dataArray addObject:result];
}
}
};
ALAssetsLibraryGroupsEnumerationResultsBlock
libraryGroupsEnumeration = ^(ALAssetsGroup* group, BOOL* stop){
//within the group enumeration block.filter to enumerate just photos.
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
if (group!=nil) {
NSString *g=[NSString stringWithFormat:#"%#",group];
NSLog(#"gg:%#",g);//gg:ALAssetsGroup - Name:Camera Roll, Type:Saved Photos, Assets count:71
[group enumerateAssetsUsingBlock:groupEnumerAtion];
}
else {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self saveToDB:self._dataArray];
});
}
};
Assume that my Camera Roll have 100 photos,and I want get first 30 to save to my database.but in above code ,I must wait for 100 results fisishing.After 30 write to database , continue to get another 30 until end.
because get 100 or even more photos will delay my UI refresh.It looks not comfortable.
Thanks a lot!
what should I write.?
Try this one
if (result!=NULL) {
if ([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) {
[self._dataArray addObject:result];
if([self._dataArray count] == 30){
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSArray *array = [[NSArray alloc] initWithArray:self._dataArray]; //please change the array declaration to top of this method. Because the Block will not allow to do it here.
[self._dataArray removeAllObjects];
[self saveToDB:array];
//array release,if not using ARC
});
}
}
}
and
if (group!=nil) {
[group enumerateAssetsUsingBlock:groupEnumerAtion];
}
else if([self._dataArray count] > 0) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self saveToDB:self._dataArray];
});
}
You should try saving to the database on a background thread, and let CoreData (assuming you use CoreData) update the NSManagedObjectContext on the main thread by handling NSManagedObjectContextDidSaveNotification.
You could still save after 30 photos, that might relieve some of the I/O stress, but you'd have to test performance.

The execution of block is always delayed

I'm pretty new to IOS. I want to get all pictures on a device in viewDidLoad. but the block is always executed after I called NSLog(#"%d", photos.count) in the code as follows. How to handle such a case?
__block NSMutableArray* photos = [[[NSMutableArray alloc] init] retain];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
void (^assertsEnumerator)(ALAsset *, NSUInteger , BOOL *) = ^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if(result)
{
NSLog(#"Assert name : %#", [result valueForProperty:ALAssetPropertyURLs]);
[photos addObject:[UIImage imageWithCGImage:[result aspectRatioThumbnail]]];
}
};
void (^groupEnumerator)(ALAssetsGroup*, BOOL*) = ^(ALAssetsGroup *group, BOOL *stop) {
if(group != nil) {
NSLog(#"Adding group %#", [group valueForProperty:ALAssetsGroupPropertyName]);
[group enumerateAssetsUsingBlock: assertsEnumerator];
}
};
[library enumerateGroupsWithTypes: ALAssetsGroupSavedPhotos
usingBlock:groupEnumerator
failureBlock:^(NSError * err) {NSLog(#"Erorr: %#", [err localizedDescription]);}];
[library release];
NSLog(#"%d", photos.count);
[photos release];
As the ALAssetsLibrary documentation states, enumerateGroupsWithTypes:usingBlock:failureBlock: is asynchronous, so instead of being called in place, it's scheduled to be called from within the next run loop cycle. The documentation states clearly what's the reason for that and how you should proceed:
This method is asynchronous. When groups are enumerated, the user may be asked to confirm the application's access to the data; the method, though, returns immediately. You should perform whatever work you want with the assets in enumerationBlock.

Resources