clx
clx

Reputation: 175

NSFetchRequest with large fetchOffet cause NSRangeException

Does anyone know why executing a NSFetchRequest with fetchOffset set beyond the total number of matching records will cause a NSRangeException? Consider the following code:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyRecord" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"checked == 1"];
[fetchRequest setPredicate:predicate];
fetchRequest.fetchOffset = 20;
fetchRequest.fetchLimit = 20;

NSError *error = nil;
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];

If I only have 10 records in the core data storage but none of the matches the predicate filter, the last line will cause a NSRangeException:

  *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSMutableArray       removeObjectsInRange:]: range {0, 20} extends beyond bounds for empty array'
  *** First throw call stack:
  (
    0   CoreFoundation                      0x04ee61e4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x04c658e5 objc_exception_throw + 44
    2   CoreFoundation                      0x04edae8d -[NSMutableArray removeObjectsInRange:] + 301
    3   CoreData                            0x017b4842 -[NSMappedObjectStore executeFetchRequest:withContext:] + 3762
    4   CoreData                            0x017b391e -[NSMappedObjectStore executeRequest:withContext:error:] + 222
    5   CoreData                            0x0173b7f2 -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 4466
    6   CoreData                            0x01738f56 -[NSManagedObjectContext executeFetchRequest:error:] + 566
  ....
  )

Not sure if it matters, but the managed object context has a NSInMemoryStoreType storage type, and I did try saving the context before executing the query. What's interesting is that if my core data storage contains no "MyRecord" objects, the above code will work correctly and returns no result. But as soon as I have some objects (less than the fetchOffset count) the storage, I run into this issue.

Anyone know why does this happen?

Upvotes: 1

Views: 248

Answers (2)

quellish
quellish

Reputation: 21244

If you look at the documentation for fetchLimit :

If you set a fetch limit, the framework makes a best effort, but does not guarantee, to improve efficiency. For every object store except the SQL store, a fetch request executed with a fetch limit in effect simply performs an unlimited fetch and throws away the unasked for rows.

The exception you're running into smells like a bug. As the documentation describes, for a store other than the NSSQLiteStoreType, the rows outside fetchLimit are discarded - that is the removeObjectsInRange: in your stack trace. The caller is not obligated to see how many objects a fetch would return before executing a fetch (and that number could change in between that check and the actual fetch), and the framework should "do the right thing" in these cases - before removing rows from the results, it should be performing a bounds check.

Please file a bug

Upvotes: 1

Léo Natan
Léo Natan

Reputation: 57050

Uhm, just like trying to access a range that is incorrect in an array, similar result will happen when trying to fetch objects with an incorrect offset.

Use -[NSManagedObjectContext countForFetchRequest:error:] to find the number of registered objects in the context, and then perform some logic to ensure the range you provide is correct.

Upvotes: 0

Related Questions