ALAssetPrivate past the lifetime of its owning ALAssetsLibrary - ios

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.

Related

iOS - Bad Access when calling ALAssetsLibrary several times

When a user uploads a picture from the iPhone gallery, I extract the location using ALAssetsLibrary:
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL:urlPhoto
resultBlock:^(ALAsset *asset) {
CLLocation *location = [asset valueForProperty:ALAssetPropertyLocation];
} failureBlock:^(NSError *error) {
NSLog(#"Can not get asset - %#",[error localizedDescription]);
}];
However, if the user uploads a picture and then returns to the upload screen and upload another, after three or four uploads the app crashes on EXC_BAD_ACCESS, when running the assetForURL:resultBlock:failureBlock: method.
I guess it happens since the assetForURL:resultBlock:failureBlock: runs async and the ALAssetsLibrary is released for some reason, though it cannot run simultaneously the way I've built the app.
How can I prevent it from crashing?
EDIT
Although the crash was always at this point (due to the earlier dismiss), the error occurred because of a UITableView that was deallocated earlier, but at this point referred to its delegate/datasource.
the fix was adding:
- (void) dealloc
{
myTableView.dataSource = nil;
myTableView.delegate = nil;
}
at the end of the UIViewController that had the TableView.
Just change your object to property .
interface:
#property (nonatomic, strong) ALAssetsLibrary* assetslibrary;
Than call implementation:
if (self.assetslibrary == nil) {
self.assetslibrary = [[ALAssetsLibrary alloc] init];
}
[self.assetslibrary assetForURL:urlPhoto
resultBlock:^(ALAsset *asset) {
CLLocation *location = [asset valueForProperty:ALAssetPropertyLocation];
} failureBlock:^(NSError *error) {
NSLog(#"Can not get asset - %#",[error localizedDescription]);
}];

Can't add ALAssets to NSOperationQueue from enumeration block

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.)

getting the posterImage of ALAssetsGroupAlbum by name of album

Struggling a bit here with blocks and the ALAssetLibrary routines. I've been through the WWDC + Stanford videos on blocks and read up a bit but it hasn't quite clicked for me yet.
What I am trying to do is get the posterImage for a custom photo library. The first routine below is in my main viewController and calls the function getPosterImageForAlbum: which is in an ALAssetLibrary class extension.
The things I am struggling with/confused about/have tried:
Should I pass in a reference (CGImageRef or UIImage) and have the
ALAssetLibrary method set it or define a return value for the
ALAssetLibrary method and then handle the image in my main class?
I've tried it both ways without success.
The asynchronous nature of the ALAssetLibrary enumeration methods are
kind of hard to work with - so I guess I am doing it wrong.
Defining a block to be passed as a param: do I always need to
typedef it?
I think I've got all the conceptual bits and pieces swirling around but I haven't yet been able to wrangle them into a clear understanding of working with blocks. Any tips or pointers* to good articles would be much appreciated.
//
- (IBAction)getPosterImage:(id)sender {
NSString *groupName = self.groupNameField.text;
NSLog(#"%#", groupName);
__weak typeof(self) weakSelf = self;
CGImageRef tmpImg = [weakSelf.library getPosterImageForAlbum:groupName withCompletionBlock:(CGImageRef)(^GetPosterImageCompletion)(NSError *error){
if (error!=nil) {
NSLog(#"getPosterImage error: %#", [error description]);
} else {
if (tmpImg != nil){
UIImage * posterImg = [UIImage imageWithCGImage:tmpImg];
weakSelf.pImage.image = posterImg;
}
}
}];
}
// This is in an extension of the ALAssetLibrary
typedef CGImageRef(^GetPosterImageCompletion)(NSError* error);
-(CGImageRef)getPosterImageForAlbum:(NSString*)albumName
withCompletionBlock:(GetPosterImageCompletion)completionBlock
{
__block BOOL albumWasFound = NO;
__block CGImageRef thePosterImage = nil;
SaveImageCompletion test;
//search all photo albums in the library
[self enumerateGroupsWithTypes:ALAssetsGroupAlbum
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
NSLog(#"this group name: %#",
[group valueForProperty:ALAssetsGroupPropertyName]);
//compare the names of the albums
if ([albumName compare:
[group valueForProperty:ALAssetsGroupPropertyName]]==NSOrderedSame) {
printf("matches \n"); //target album is found
albumWasFound = YES;
thePosterImage = group.posterImage;
*stop = true;
return ;
}
} failureBlock: test];
if (albumWasFound==NO) {
NSLog(#"%#", #"No group found");
}
return thePosterImage;
}
I'm editing my original answer in a big way because only after I posted it did I really understand what you are trying to do -- write a class extension on ALAssetLibrary that finds an album's poster image by name, rather than just find an album's poster image by name in any way possible.
I would handle this by writing an extension method that takes, as a parameter, a completion block that accepts a single parameter of type UIImage (or optionally an NSError parameter as well). In that completion block, the caller can do whatever they want with the image that's asynchronously returned. There is no need to typedef the block -- you could write the method this way:
- (void) getPosterImageForAlbumNamed: (NSString *) albumName completionBlock: (void (^)(UIImage *, NSError *)) completionBlock
{
__block ALAssetsGroup * foundAlbum = nil;
[self enumerateGroupsWithTypes: ALAssetsGroupAlbum
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group) {
if ([(NSString *) [group valueForProperty: ALAssetsGroupPropertyName] compare: albumName] == NSOrderedSame) {
*stop = YES;
foundAlbum = group;
completionBlock([UIImage imageWithCGImage: [group posterImage]], nil);
}
} else{
if (! foundAlbum) {
NSLog(#"Album wasn't found");
// completionBlock(nil, XX SOME ERROR XX);
}
}
} failureBlock:^(NSError *error) {
NSLog(#"Couldn't access photo library");
// completionBlock(nil, XX SOME ERROR XX);
}];
}
Then, you could call the method this way:
-(IBAction) getPosterForAlbum: (id) sender
{
NSString * albumName = self.textField.text;
ALAssetsLibrary * library = [[ALAssetsLibrary alloc] init];
__weak typeof(self) weakSelf = self;
[library getPosterImageForAlbumNamed: albumName completionBlock:^(UIImage * image, NSError * error) {
if (! error) {
[weakSelf doSomethingWithPosterImage: image];
}
}];
}
Is that along the lines of what you are trying to do? (Sorry for the major edit...)

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