Результаты пейджинга из запросов Core Data
У меня есть относительно простая база данных sqlite базы данных. Я пытаюсь получить результаты от DB на одной странице за раз.
NSFetchRequest* request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[...]];
[request setPredicate:[NSPredicate predicateWithFormat:@"flaggedTime != nil"]];
NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"flaggedTime" ascending:NO];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[request setFetchLimit:pageSize];
[request setFetchOffset:((pageIndex - 1) * pageSize)];
NSArray* results = [self.context executeFetchRequest:request error:NULL];
pageSize 30, pageIndex при тестировании данных - 1, 2, 3 или 4 (в базе данных около 80 элементов, поэтому pageIndex = 4 не должен возвращать элементы).
Предикат и сортировка отлично работают, результаты успешно возвращаются. Предел выборки отлично работает. Ошибки не возвращаются.
Проблема. Я всегда получаю результаты с первой страницы, как если бы fetchOffset не был установлен. Я попытался удалить предикат и сортировку, но безрезультатно.
Единственная ситуация, когда я мог сделать работу fetchOffset, - это когда я использовал значения до 30. Конечно, это бессмысленно для пейджинга...
Кто-нибудь знает, почему? Я буду благодарен за каждый ответ.
Обновление: я говорю о iOS. Протестировано на 4.2 и 5.0.
Обновление 2: Чтобы упростить задачу.
NSFetchRequest* request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[...];
NSError* error = nil;
NSManagedObjectContext* context = [...];
NSUInteger count = [context countForFetchRequest:request error:&error];
assert(error == nil);
NSLog(@"Total count: %u", count);
request.fetchOffset = 0;
request.fetchLimit = 30;
NSLog(@"Fetch offset: %u, limit: %u", request.fetchOffset, request.fetchLimit);
NSArray* page1 = [context executeFetchRequest:request error:&error];
assert(error == nil);
NSLog(@"Page 1 count: %u", page1.count);
request.fetchOffset = 30;
request.fetchLimit = 30;
NSLog(@"Fetch offset: %u, limit: %u", request.fetchOffset, request.fetchLimit);
NSArray* page2 = [context executeFetchRequest:request error:&error];
assert(error == nil);
NSLog(@"Page 2 count: %u", page2.count);
дает:
Total count: 34
Fetch offset: 0, limit: 30
Page 1 count: 30
Fetch offset: 30, limit: 30
Page 2 count: 30 (ERROR: should give 4)
Ответы
Ответ 1
Свойство pageOffset
NSFetchRequest
(иногда?) игнорируется при запуске запросов на выборку из несохраненного контекста. Обратите внимание, что в коде по контексту OP никогда не сохраняется, в то время как в фрагменте кода, прикрепленном к ответу @kcharwood, он фактически сохраняется. Вот пример:
- (void) printArrayOfTestEntities:(NSArray *)array{
NSMutableString * s = [NSMutableString new];
NSArray * numbers = [array valueForKey:@"someField"];
for (NSNumber * number in numbers){
[s appendString:[NSString stringWithFormat:@"%d ", [number intValue]]];
}
NSLog(@"fetched objects: %@ \rcount: %d", s, array.count);
}
/* example itself */
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[context setPersistentStoreCoordinator:persistentStoreCoordinator];
for(int i = 0; i < 34;i++){
NSManagedObject * object = [NSEntityDescription insertNewObjectForEntityForName:@"TestEntity"
inManagedObjectContext:context];
[object setValue:@(i) forKey:@"someField"];
}
NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"TestEntity"];
request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"someField" ascending:YES]];
request.fetchLimit = 30;
NSArray * result = [context executeFetchRequest:request error:nil];
[self printArrayOfTestEntities:result];
request.fetchOffset = 30;
result = [context executeFetchRequest:request error:nil];
[self printArrayOfTestEntities:result];
[context save:&error];
request.fetchOffset = 0;
result = [context executeFetchRequest:request error:nil];
[self printArrayOfTestEntities:result];
request.fetchOffset = 30;
result = [context executeFetchRequest:request error:nil];
[self printArrayOfTestEntities:result];
Журнал:
2014-03-01 11:30:06.986 coredatatest[19771:70b] fetched objects: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
count: 30
2014-03-01 11:30:06.990 coredatatest[19771:70b] fetched objects: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
count: 30
2014-03-01 11:30:06.995 coredatatest[19771:70b] fetched objects: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
count: 30
2014-03-01 11:30:06.997 coredatatest[19771:70b] fetched objects: 30 31 32 33
count: 4
Ответ 2
Я только что создал демонстрационный проект, который пытается воссоздать ваш сценарий в его простейшей форме. Я создал пустой проект, добавил 34 объекта, а затем запросил его с тем же кодом, который вы указали выше. Ниже приведен пример:
CDAppDelegate * delegate = (CDAppDelegate*)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext * context = [delegate managedObjectContext];
for(int i = 0; i < 34;i++){
CDObject * object = [NSEntityDescription insertNewObjectForEntityForName:@"CDObject"
inManagedObjectContext:context];
[object setValue:i];
}
[delegate saveContext];
NSFetchRequest* request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:@"CDObject"
inManagedObjectContext:context]];
NSError* error = nil;
NSUInteger count = [context countForFetchRequest:request error:&error];
assert(error == nil);
NSLog(@"Total count: %u", count);
request.fetchOffset = 0;
request.fetchLimit = 30;
NSLog(@"Fetch offset: %u, limit: %u", request.fetchOffset, request.fetchLimit);
NSArray* page1 = [context executeFetchRequest:request error:&error];
assert(error == nil);
NSLog(@"Page 1 count: %u", page1.count);
request.fetchOffset = 30;
request.fetchLimit = 30;
NSLog(@"Fetch offset: %u, limit: %u", request.fetchOffset, request.fetchLimit);
NSArray* page2 = [context executeFetchRequest:request error:&error];
assert(error == nil);
NSLog(@"Page 2 count: %u", page2.count);
[request release];
Журнал выглядит так:
2011-11-04 14:53:04.530 CDCoreDataTest[77964:207] Total count: 34
2011-11-04 14:53:04.531 CDCoreDataTest[77964:207] Fetch offset: 0, limit: 30
2011-11-04 14:53:04.532 CDCoreDataTest[77964:207] Page 1 count: 30
2011-11-04 14:53:04.533 CDCoreDataTest[77964:207] Fetch offset: 30, limit: 30
2011-11-04 14:53:04.533 CDCoreDataTest[77964:207] Page 2 count: 4
Используя ваш код, я смог его запустить и без проблем. Это было сделано с iOS 5.0, запущенным на симуляторе. Ваш код выглядит правильно для меня, потому что вы пытаетесь выполнить, так что должно быть что-то происходит с запросом на выборку или самим контекстом...
Ответ 3
Core Data имеет встроенный пейджинг и он просто супер, просто установите fetchBatchSize в запросе выборки. Когда заданы только запросы на выборку для всех объектов как сбои, в основном просто указатель и его индекс строки, который является минимальной памятью в специальный массив, называемый массивом сбоев партии. Я предполагаю, что это нормально в большинстве случаев, хотя я не задумывался о том, сколько памяти он будет использовать для огромного количества строк. Затем, когда вы зацикливаете этот массив при доступе к свойству с ошибкой записи, он запрашивает базу данных для этой записи, а следующие записи - до номера, установленного для размера партии. Это позволяет вам зацикливать результаты один за другим, как обычно, но в фоновом режиме он загружается в данные партиями. Начиная с iOS 9.2 минимальный размер партии равен 4, поэтому нет точки, устанавливающей число ниже этого.