app crashes when retrieving the ALAssets from ALAssetsGroupSavedPhotos group up on receiving ALAssetsLibraryChangedNotification - alassetslibrary

Part of my app has a photo browser similar to Apple's Photos app (Grid like view). To refresh my photos whenever there is any change in original photo app, i registered for ALAssetsLibraryChangedNotification
self.assetsLibrary = [[ALAssetsLibrary alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveLibraryChangedNotification:) name:ALAssetsLibraryChangedNotification object:self.assetsLibrary];
In "receiveLibraryChangedNotification" method - i check if the userInfo has ALAssetLibraryUpdatedAssetsKey & then call the refresh photos method.
- (void) receiveLibraryChangedNotification:(NSNotification *) notif{
NSDictionary *userInfo = [notif userInfo];
if(userInfo){
id updatedAssets = [userInfo objectForKey:ALAssetLibraryUpdatedAssetsKey];
if(updatedAssets){
[self refreshPhotos];
}
}
- (void) refreshPhotos {
self.assetArray = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^(void){
[self.assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if(group){
[group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [group numberOfAssets])] options:0 usingBlock:^(ALAsset *result, NSUInteger index, BOOL *shouldStop) {
if(result){
if([[result valueForProperty:#"ALAssetPropertyType"] isEqualToString:#"ALAssetTypePhoto"]){
[self.assetArray addObject:result];
}
}
}];
}
} failureBlock:^(NSError *error) {
DebugLog(#"error >>> %#",[error description]);
}];
});
}
Problem i am facing is that some times the notification is being triggered multiple times & the app crashes at
[self.assetArray addObject:result];
with error -
malloc: *** error for object 0x4aa9000: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
or
malloc: *** error for object 0x16fd3e74: incorrect checksum for freed object - object was probably modified after being freed.
*** set a breakpoint in malloc_error_break to debug
also some times i am not receiving the ALAssetLibraryUpdatedAssetsKey in the userInfo of notification, so the photos are never refreshed.
can some one guide me in correct direction.
Thanks in advance.

Allocate self.assetArray after the nil assignment or write [self.assetArray removeAllObjects] instead of assigning it to nil.
- (void) refreshPhotos {
self.assetArray = nil;
self.assetArray = [[NSMutableArray alloc] init];
//or remove the top 2 lines and try `[self.assetArray removeAllObjects]`
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^(void){
[self.assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if(group){
[group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [group numberOfAssets])] options:0 usingBlock:^(ALAsset *result, NSUInteger index, BOOL *shouldStop) {
if(result){
if([[result valueForProperty:#"ALAssetPropertyType"] isEqualToString:#"ALAssetTypePhoto"]){
[self.assetArray addObject:result];
}
}
}];
}
} failureBlock:^(NSError *error) {
DebugLog(#"error >>> %#",[error description]);
}];
});
}
also some times i am not receiving the ALAssetLibraryUpdatedAssetsKey
in the userInfo of notification,
there are four keys used to get values from the user information dictionary of the ALAssetsLibraryChangedNotification notification.
NSString * const ALAssetLibraryUpdatedAssetsKey;
NSString * const ALAssetLibraryInsertedAssetGroupsKey;
NSString * const ALAssetLibraryUpdatedAssetGroupsKey;
NSString * const ALAssetLibraryDeletedAssetGroupsKey;

Related

AlAssetLibrary returning empty results

I'm trying to pick out some camera roll meta data. When I enumerate through the assets, I cannot seem to retrieve any information and get an empty array. Is there a step I'm missing?
My code:
assets = [[NSMutableArray array] init];
void (^assetEnumerator)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
if(asset != NULL) {
[assets addObject:asset];
dispatch_async(dispatch_get_main_queue(), ^{
});
}
};
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) {
if(group != nil) {
[group enumerateAssetsUsingBlock:assetEnumerator];
}
};
library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
usingBlock:assetGroupEnumerator
failureBlock: ^(NSError *error) {
NSLog(#"Failed.");
}];
NSLog(#"%#", assets); //prints an empty array
Midhun MP is right that you are not waiting for the asynchronous enumeration to complete. In this case, you have asynchronous blocks calling other asynchronous blocks, so it is not simple to know when all enumeration is done.
If you would like to know when this is done, and end up with an array that contains all of the enumerated assets, you could use dispatch_groups. Here is one way you could do that (I've included multiple ALAssetGroup types to show that this can work with multiple albums):
dispatch_group_t loadingGroup = dispatch_group_create();
NSMutableArray * assets = [[NSMutableArray array] init];
NSMutableArray * albums = [[NSMutableArray array] init];
void (^assetEnumerator)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
if(index != NSNotFound) {
[assets addObject:asset];
dispatch_async(dispatch_get_main_queue(), ^{ });
} else {
dispatch_group_leave(loadingGroup);
}
};
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) {
if(group != nil) {
[albums addObject: group];
} else {
NSLog(#"Found %d albums", [albums count]);
// album loading is done
for (ALAssetsGroup * album in albums) {
dispatch_group_enter(loadingGroup);
[album enumerateAssetsUsingBlock: assetEnumerator];
}
dispatch_group_notify(loadingGroup, dispatch_get_main_queue(), ^{
NSLog(#"DONE: ALAsset array contains %d elements", [assets count]);
});
}
};
ALAssetsLibrary * library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos | ALAssetsGroupAlbum
usingBlock:assetGroupEnumerator
failureBlock: ^(NSError *error) {
NSLog(#"Failed.");
}];
(In this example, it is safe to have various blocks adding to assets and albums because the enumeration is all happening on the main thread.)
If you are running this on iOS 6, it may be that the user has denied access for you app to access the asset library. If this is the case, the failureBlock is called.
Also note that the usingBlock is called asynchronously so your attempt to log assets is premature. You should move the NSLog statement into the end of the enumeration block.
From the docs for enumerateGroupsWithTypes:usingBlock:failureBlock:
The results are passed one by one to the caller by executing the enumeration block.
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.
If the user denies access to the application, or if no application is allowed to access the data, the failureBlock is called.
Your NSLog will always display an empty array because the NSLog statement will work before completing the asynchronous enumeration block.
Solution:
First check that your photolibrary is not empty.
Then add NSLog in:
void (^assetEnumerator)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
if(asset != NULL) {
[assets addObject:asset];
NSLog(#"Asset : %#", asset);
dispatch_async(dispatch_get_main_queue(), ^{
});
}
};

IOS Social framework and image post

I am experimenting with the Social Framework.
I was wondering if there is any possible way to attach the last photo taken and currently saved in the camera roll using this implementation from my app:
- (IBAction)shareByActivity:(id)sender {
NSArray *activityItems;
if (self.sharingImage != nil) {
activityItems = #[self.sharingImage, self.sharingText, self.addURL];
} else {
activityItems = #[self.sharingText, self.addURL];
}
UIActivityViewController *activityController =
[[UIActivityViewController alloc] initWithActivityItems:activityItems
applicationActivities:nil];
[self presentViewController:activityController
animated:YES completion:nil];
}
If so how do I modify the shareImage name in this specific portion of my -(void)viewDidLoad ?
self.sharingImage = [UIImage imageNamed:#"notSureWhatToPutHere"];
Everything works well, the social panel opens and has all the needed service. My only request is to find out how to call the latest image from the camera roll.
I am not sure if I need the image real name or if there is any other way to achieve what i would like: a post with a picture attached (most recent picture in camera roll).
Any help is appreciated.
You can pretty painlessly get the last image in the camera roll using the asset library framework. Give this a try:
#import <AssetsLibrary/AssetsLibrary.h>
Then to get the image:
ALAssetsLibrary *cameraRoll = [[ALAssetsLibrary alloc] init];
[cameraRoll enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *images, BOOL *stop) {
[images setAssetsFilter:[ALAssetsFilter allPhotos]];
[images enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:[images numberOfAssets] - 1] options:0 usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop){
if (result) {
ALAssetRepresentation *rep = [result defaultRepresentation];
self.sharingImage = [UIImage imageWithCGImage:[rep fullScreenImage]];
}
}];
}
failureBlock: ^(NSError *error) {
NSLog(#"An error occured: %#",error);
}];
Thanks for the great help NSPostWhenIdle!
I was able to resolve the issue of getting "newest photos" from camera roll by moving my ALAssettsLibrary chuck of code away from viewDidLoad and created a refreshAlbum
- (void) refreshAlbum
{
ALAssetsLibrary *cameraRoll = [[ALAssetsLibrary alloc] init];
[cameraRoll enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *images, BOOL *stop) {
[images setAssetsFilter:[ALAssetsFilter allPhotos]];
[images enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:[images numberOfAssets] - 1] options:0 usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if (result) {
ALAssetRepresentation *rep = [result defaultRepresentation];
self.sharingImage = [UIImage imageWithCGImage:[rep fullScreenImage]];
}
}];
}
failureBlock: ^(NSError *error) {
NSLog(#"An error occured: %#",error);
}];
}
Then I am calling the newly created refreshAlbum from the same button that enable the shareByActivity
- (IBAction)refreshButton:(id)sender {
[self refreshAlbum];
}
This work particularly well as my app does not have any picture to share as it starts up.
The first time that imageShare become available is after activating the shareByActivity button that now carries the refreshButton function as well!
Thanks again, this really work.

Memory usage climbing when popping out of ALAssetslibrary during enumeration

I have an issue where I see memory usage climbing (but no obvious leaks in Instruments) in my app.
I have a test project with two viewControllers: MainViewController and PhotoViewController. The MainViewController contains a single button that simply pushes PhotoViewController via a uinavigationcontroller pushViewController method.
In PhotoViewController, I am using the ALAssetsLibrary to populate a UITableView with images. I essentially do this in two parts. First, I check to see what assetGroups are available, as I need to show images from the Camera Roll and the Photolibrary. Once that is done, I call another method to enumerate through the actual Assets.
Here is the strange behavior: if I push the PhotoViewController and let it finish the entire enumeration and populate the UITableView, and then pop out back to the MainViewController, everything is fine.
However, if I repeatedly and rapidly push and pop out of the PhotoViewCOntroller (while it hasn't yet finished enumerating and populating the UITableiew),then I see my memory usage gradually climbing until the app finally dies. I don't see any obvious leaks in Instruments.
I don't know the relevant code, but here are two methods that use to enumerate. Of course, in dealloc, I am releasing the relevant ivars.
Is there some way to cancel an enumeration upon pop?
Just as a note, I am basing my test code off this project (https://github.com/elc/ELCImagePickerController), although heavily customized. However, I just tested with that code and the same issue happens. Note that you would only see memory usage climb if you have sufficient ALAssets to enumerate. If there are too few, then it would finish enumerating beforeyou couldpop back out.
Thank you!
- (void)getAssetGroups
{
// Load Albums into assetGroups
dispatch_async(dispatch_get_main_queue(), ^
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Group enumerator Block
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop)
{
if (group == nil)
{
// check what data is available
if([savedPhotosGroup numberOfAssets] > 0 && [libraryGroup numberOfAssets] > 0)
{
// User has both Camera Roll and Photo Library albums
self.tableData = [NSMutableDictionary dictionaryWithObjectsAndKeys:
savedPhotoAssets, NSLocalizedString(#"PHOTOPICKER_CAMERAROLL", nil),
libraryPhotosAssets, NSLocalizedString(#"PHOTOPICKER_PHOTOLIBRARY", nil),
nil];
self.sectionKeys = [NSArray arrayWithObjects:NSLocalizedString(#"PHOTOPICKER_CAMERAROLL", nil), NSLocalizedString(#"PHOTOPICKER_PHOTOLIBRARY", nil), nil];
}
else if([libraryGroup numberOfAssets] == 0)
{
// User only has Camera Roll
self.tableData = [NSMutableDictionary dictionaryWithObjectsAndKeys:
savedPhotoAssets, NSLocalizedString(#"PHOTOPICKER_CAMERAROLL", nil),
nil];
self.sectionKeys = [NSArray arrayWithObjects:NSLocalizedString(#"PHOTOPICKER_CAMERAROLL", nil), nil];
}
else
{
//User only has Photo Library
self.tableData = [NSMutableDictionary dictionaryWithObjectsAndKeys:
libraryPhotosAssets, NSLocalizedString(#"PHOTOPICKER_PHOTOLIBRARY", nil),
nil];
self.sectionKeys = [NSArray arrayWithObjects:NSLocalizedString(#"PHOTOPICKER_PHOTOLIBRARY", nil), nil];
}
NSLog(#"Done enumerating groups");
[self performSelectorInBackground:#selector(enumeratePhotos) withObject:nil];
[self.tview performSelector:#selector(reloadData) withObject:nil afterDelay:1];
return;
}
ALAssetsGroupType groupType = [[group valueForProperty:ALAssetsGroupPropertyType] unsignedIntValue];
if(groupType == ALAssetsGroupSavedPhotos)
{
self.savedPhotosGroup = group;
}
else if(groupType == ALAssetsGroupLibrary)
{
self.libraryGroup = group;
}
};
// Group Enumerator Failure Block
void (^assetGroupEnumberatorFailure)(NSError *) = ^(NSError *error) {
NSLog(#"A problem occured %#", [error description]);
};
// Enumerate Albums
[library enumerateGroupsWithTypes: ALAssetsGroupSavedPhotos | ALAssetsGroupLibrary
usingBlock:assetGroupEnumerator
failureBlock:assetGroupEnumberatorFailure];
NSLog(#"Draining pool");
[pool drain];
});
}
-(void)enumeratePhotos {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"enumerating photos");
[savedPhotosGroup enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if(result == nil)
{
return;
}
CustomAsset *customAsset = [[[CustomAsset alloc] initWithAsset:result] autorelease];
[customAsset setParent:self];
[savedPhotoAssets addObject:customAsset];
}];
[libraryGroup enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if(result == nil)
{
return;
}
CustomAsset *customAsset = [[[CustomAsset alloc] initWithAsset:result] autorelease];
[customAsset setParent:self];
[libraryPhotosAssets addObject:customAsset];
}];
NSLog(#"done enumerating photos");
[tview performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
// only do this if I want to re-select some assets
if(assetsToRestore)
{
for(NSDictionary *dict in assetsToRestore)
{
NSIndexPath *indexPathToRestore = [dict objectForKey:#"selectedAssetIndexPath"];
int tagToRestore = [[dict objectForKey:#"selectedAssetTag"] intValue];
[self selectAssetWithIndexPath:indexPathToRestore andIndex:tagToRestore];
}
}
[pool drain]; }
correct me if i'm wrong, I thought using autorelease pools was supposed to be done like this now:
#autoreleasepool {
(statements)
}
That worked for me.

loadView is faster than getting images via Assets

So, in init function I am getting images via AssetsLibrary
//initWithNibName:
photoArray = [[NSMutableArray alloc ]init];
ALAssetsLibrary *asset = [[ALAssetsLibrary alloc] init];
void (^enumerateGroup)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if (result != nil) {
[photoArray addObject:result];
NSLog(#"%#", result);
}
};
void (^enumerationBlock)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop)
{
if (group != nil) {
[group enumerateAssetsUsingBlock:enumerateGroup];
}
};
[asset enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:enumerationBlock
failureBlock:^(NSError *error) {NSLog(#"Something went wrong");}];
[asset release];
//loadView
- (void)loadView
{
UIView *view = [[UIView alloc ] init];
NSLog(#"%d", [photoArray count]);
self.view = view;
[view release];
}
Log from console:
2011-06-24 18:55:12.255 xxx[9450:207] 0 //
2011-06-24 18:55:12.306 xxx[9450:207] ALAsset - Type:Photo, URLs:{
"public.jpeg" = "assets-library://asset/asset.JPG?id=1000000001&ext=JPG";
And I am confused. As you can see in the log, loadView executed code faster than initWithNibName. This is because getting images via AssetLibrary take some time. But I think all of this code is executing in one thread, so loadView should wait, for initWithNibName.
The documentation for -enumerateGroupsWithTypes:... and -enumerateAssetsUsingBlock:... doesn't say that those methods execute synchronously. From what you've found, it looks like they do their enumeration on a different thread so that you don't have to wait.

Access a single image with ALAssetsLibrary

Allow me to preface this by saying this is my first time using the ALAssetsLibrary. I need to access the most recent photo in the user's saved photo gallery. It seems that to do this, I have to create an ALAssetsLibrary instance and iterate over every item in the user's gallery before selecting the last image. This is always worst-case scenario. Is there a faster/better way to approach this problem?
You don't have to enumerate all the photos in the user's gallery.
The ALAssetsGroup class has a method - (void)enumerateAssetsAtIndexes:(NSIndexSet *)indexSet options:(NSEnumerationOptions)options usingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock which you can use to indicate which assets you want to enumerate.
In your case it's only the last one so set indexSet to [NSIndexSet indexSetWithIndexesInRange:NSMakeRange([group numberOfAssets]-1, [group numberOfAssets]) where group is your ALAssetsGroup.
As #mithuntnt mentioned, you can get the ALAssetsGroup for the photo library using [[assetsLibrary] enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:^(ALAssetsGroup *group, BOOL *stop)
What about this:
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
*stop = YES;
//...
}
}];
http://developer.apple.com/library/ios/#documentation/AssetsLibrary/Reference/ALAssetsLibrary_Class/Reference/Reference.html
There is only one enumeration method. So this is the only way.
I needed the last imported photos. You can have some filter similar to this.
[[assetsLibrary] enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if( group )
{
NSString * groupName = [group valueForProperty:ALAssetsGroupPropertyName];
if( [#"Last Import" isEqualToString:groupName] )
{
*stop = true;
...
The accepted answer doesn't appear to work if you're enumerating an ALAssetGroup that you've set a filter on (because [group numberOfAssets] returns the total assets rather then the total assets after filtering).
I used this:
typedef void(^SMKMostRecentPhotoCompletionBlock)(ALAsset *asset);
- (void)mostRecentPhotoWithCompletionBlock:(SMKMostRecentPhotoCompletionBlock)completionBlock
{
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
__block ALAsset *mostRecentPhoto = nil;
if (group)
{
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result != NULL)
{
mostRecentPhoto = result;
*stop = YES;
}
}];
}
if (completionBlock)
{
completionBlock(mostRecentPhoto);
}
} failureBlock:^(NSError *error) {
if (completionBlock)
{
completionBlock(nil);
}
}];
}
In your completionBlock, make sure to check that the returned ALAsset != nil.