5

When I use ALAssetsLibrary to get local photos it works fine. But after I delete some photos with the 'Photos' application my app crashes.

Crash info is:

"Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSOrderedSet enumerateObjectsAtIndexes:options:usingBlock:]: index 14 beyond bounds [0 .. 9]'".'14'

It looks like the number of local photos still remains the same as befoore. And even after I quit my app and restart it again, it still crashes.

Local photo access code:

dispatch_async(dispatch_get_main_queue(), ^
{
   @autoreleasepool 
   {
       ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
       {
           NSLog(@"error occour =%@", [myerror localizedDescription]);
       };

       ALAssetsGroupEnumerationResultsBlock groupEnumerAtion = ^(ALAsset *result, NSUInteger index, BOOL *stop)
       {
           if (result!=NULL) 
           {
               if ([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) 
               {
                   [self.g_imageArray addObject:result];
               }
           }
       };

       ALAssetsLibraryGroupsEnumerationResultsBlock
       libraryGroupsEnumeration = ^(ALAssetsGroup* group, BOOL* stop)
       {
           if (group == nil) 
           {
               return;
           }

           if (group!=nil) {
               [group enumerateAssetsUsingBlock:groupEnumerAtion];
           }
       [self updatephotoList];
       };

       self.library = [[ALAssetsLibrary alloc] init];
       [self.library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
                              usingBlock:libraryGroupsEnumeration 
                            failureBlock:failureblock];
   }
});

If I take another photo with system camera, my application does OK again.

zhangwx
  • 53
  • 1
  • 5
  • One question: are you sure that `[self updatephotoList]` will be called _after_ the last enumeration block of `[group enumerateAssetsUsingBlock...]`? I have dozens of crashes in my app so I'll check this approach and see how it works: http://stackoverflow.com/a/23378441/649379 – SoftDesigner May 25 '15 at 15:06

7 Answers7

2

This seems to be an iOS bug, like you said ALAssetsLibrary returned the wrong number of your photos so you got index out of bounds error. The workaround is to reload your photo again like these:

ALAssetsLibraryGroupsEnumerationResultsBlock
libraryGroupsEnumeration = ^(ALAssetsGroup* group, BOOL* stop)
{
       if (group == nil) 
       {
           return;
       }
       //Force to reload photo as numberOfAssets is broken
       NSLog(@"how many picture I have in this group: %d",[group numberOfAssets]);
       [group setAssetsFilter:[ALAssetsFilter allPhotos]];//this will cause group to reload
       NSLog(@"how many picture I have in this group: %d",[group numberOfAssets]);

       if (group!=nil) {
           [group enumerateAssetsUsingBlock:groupEnumerAtion];
       }
   [self updatephotoList];
 };
Qiulang
  • 10,295
  • 11
  • 80
  • 129
1

Registering an observer did not help in my case. Users were still crashing, but I didn't. Until today.

I figured out the way to solve this crash. It's a bug in Apple's photo library in the end, but there's a workaround. What you do is, you set the filter to photo, and then to video, instead of leaving it at the default 'assets'. You then enumerate it once for each, and do some trickery to ensure you get that final 'at null' point for doing whatever updates you need. My current approach is a bit messy but you get the idea:

// pending is used to tell the block we're not done, even if result is NULL
BOOL pending = YES;
// resorted is just a flag I use in case there are no videos; if there are none, the block is never called at all, and thus the == NULL part never triggers
__block BOOL resorted = NO;

ALAssetsGroupEnumerationResultsBlock assetEnumerator = ^(ALAsset *result, NSUInteger index, BOOL *stop) {
    if(result != NULL) {
        [assets addObject:result];
    } else if (! pending) {
        // ready!!
        resorted = YES;
        [self resort]; // my own method; replace with e.g. tableView reload!
    }
};

// there are two types of assets - photos and videos; we start with photo
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
NSLog(@"assets = %d", group.numberOfAssets);
[group enumerateAssetsUsingBlock:assetEnumerator];
// we then set pending to NO; even though the enumeration happens in a separate thread, it seems like pending is not caught by the above enumeration (I have 105 images in the group I am enumerating, FWIW; it may be better to set pending in the == NULL part of the enumeration though
pending = NO;
// now we switch to video and do the same thing
[group setAssetsFilter:[ALAssetsFilter allVideos]];
BriefLog(@"assets = %d", group.numberOfAssets);
[group enumerateAssetsUsingBlock:assetEnumerator];

// if there are 0 vids, the above block is not ever called so we flip to a background thread and then back (probably not necessary) and then check if resorted is set; if it isn't we call resort
if (! resorted) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
    dispatch_async(dispatch_get_main_queue(), ^{
        if (! resorted) {
            [self resort]; // my own method; replace with e.g. tableView reload!
        }
    });
});

That's it. The NSRangeExceptions go away. At least in my case they did.

Kalle
  • 13,186
  • 7
  • 61
  • 76
  • 1
    Your `pending = NO;` has no effect. Blocks capture non-`__block` local variables by value at the time they are created. – user102008 Mar 01 '13 at 01:49
  • I'm pretty sure one of the calls got a yes and the other got a no. Will have to double check tho. – Kalle Mar 02 '13 at 10:22
0

What you need to is register an observer for ALAssetsLibraryChangedNotification to get library changes. When the notification fires, reenumerate the groups and content of the AssetsLibrary. If you don't register for the notification your application will get an old snapshot of the library and enumeration will fail. Please also note that there is a bug regarding ALAssetsLibraryChangedNotification under iOS 5.x as documented here: http://www.openradar.me/10484334

holtmann
  • 6,043
  • 32
  • 44
  • Won't help if the album you're currently viewing got removed. Yes, you will receive a notification, but the app will crash with `NSRangeException` exception when trying to re-enumerate. – Aleks N. Dec 12 '12 at 16:23
  • Even without background changes the `numberOfAssets` can be wrong on iOS 8 (GM). – Rivera Sep 11 '14 at 07:52
  • Yep, thats a bug. the numberOfAssets unfortunately includes the number of trashed/deletes photos on iOS 8 (GM) – holtmann Sep 11 '14 at 09:41
  • numberOfAssets seems to be correct under iOS 8.0.2 again. – holtmann Sep 30 '14 at 11:10
0

I started out with Qiulang answer, but it didn't work for me. What worked for me is to call the setAssetsFilter 3 times in a row with all filter combinations, before starting the enumeration.

[group setAssetsFilter:[ALAssetsFilter allPhotos]];
[group setAssetsFilter:[ALAssetsFilter allVideos]];
[group setAssetsFilter:[ALAssetsFilter allAssets]];
JP Hribovsek
  • 6,707
  • 2
  • 21
  • 26
  • Neither this or adding `numberOfAssets` in between calls worked for me to get proper photo counts. – Rivera Sep 11 '14 at 08:25
0

At iOS 8 I noticed also that numberOfAssets returns wrong number of photos if some of them were deleted and located at 'Recently Deleted' album currently.

gN0Me
  • 437
  • 1
  • 6
  • 17
0

For those of you who fill some NSArray with all the ALAssetRepresentations and don't want to change you code too much, simply filter your array with this Checker - AssetURLChecker

Cheers!

jovanjovanovic
  • 4,158
  • 3
  • 22
  • 32
0

ALAssetsLibrary library is depriciated on PHAssetsLibrary so use this code:

__block PHAssetCollection *collection;
    _arr_downloadedWallpaper = [[NSMutableArray alloc] init];

    // Find the album
    PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
    fetchOptions.predicate = [NSPredicate predicateWithFormat:@"title = %@", @"Bhakti Ras"];
    collection = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum
                                                          subtype:PHAssetCollectionSubtypeAny
                                                          options:fetchOptions].firstObject;

    PHFetchResult *collectionResult = [PHAsset fetchAssetsInAssetCollection:collection options:nil];

    [collectionResult enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop) {

        //add assets to an array for later use in the uicollectionviewcell
        NSLog(@"asset is =%@",asset);


        if (asset) {
            [self.arr_downloadedWallpaper addObject:asset];
        }
    }];
User42
  • 970
  • 1
  • 16
  • 27