0

NSFetchResultController with NSFetchRequest with fetchBatchSize = 20 always return all entities. What can it be? I didn't use sectionKeyPath and tried different sort descriptors, but it's still return all objects.

Thanks for replies!I will explain with details. I have an entity with two fields - distance and time. I have created NSFetchResultController:

func noticesFetcher() -> NSFetchedResultsController {

    let fetchRequest = NSFetchRequest()

    let defaultStore = RKManagedObjectStore.defaultStore()

    let entity = NSEntityDescription.entityForName("Notice", inManagedObjectContext: defaultStore.mainQueueManagedObjectContext)
    fetchRequest.entity = entity

    let distanceSortDescriptor = NSSortDescriptor(key: "distance", ascending: true)
    let timeSortDescriptor = NSSortDescriptor(key: "time", ascending: false)
    let sortDescriptors = [distanceSortDescriptor, timeSortDescriptor]
    fetchRequest.sortDescriptors = sortDescriptors

    fetchRequest.fetchBatchSize = 20

    let resultFetcher = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: defaultStore.mainQueueManagedObjectContext, sectionNameKeyPath: nil, cacheName: nil)

    return resultFetcher
}

But when i perform fetcher i always have all my entities in database.(100)

func performFetcher(fetcher: NSFetchedResultsController?, filter: NSPredicate?, success: (() -> ())?, failure: ((NSError) -> (NSError))?) -> Bool {

    NSFetchedResultsController.deleteCacheWithName(nil)

    var resultPerform = false

    fetcher?.fetchRequest.predicate = filter

    do {
        try fetcher?.performFetch()
        resultPerform = true
        if success != nil {
            success!();
        }
    }
    catch let error as NSError {
        if failure != nil {
            failure!(error)
        }
    }

    return resultPerform
}

What can it be? The result that i want to get is pagination getter. I know that i can do it through limit and offset, but what a problem here? Thanks

Serd
  • 421
  • 1
  • 4
  • 13

2 Answers2

6

Well, that depends on what you mean by "return all entities." I doubt it returns an array populated with all entities fully realized.

The batch size will fetch (in your case) only 20 entities at a time. Go ahead and look at the set of registered objects for the MOC and you can easily verify what is happening.

You can also fire up instruments and watch as the individual core data fetches take place.

No, batch size fetch not "only 20 entities", it fetches ALL entities. Can you make a test project and test this batch size?I'm sure you will have the same issue – Serd

@Serd, Answer providers are here to help you, and we are sometimes wrong. If you doubt their answer, then you should create a test case that demonstrates whether or not what they say is correct. Why in the world should they take extra time to do so, when you are the one with the problem, and they are the ones freely offering assistance?

If you doubt the answer, then you do the work, and if you find their answer in error, then provide either the correction or the evidence that the answer is incorrect.

However, you are new here, and in the hopes that you may learn from this experience, I'll provide you with a brief demonstration.

Setup the MOC for the test by adding NUM_OBJECTS instances to the database.

NSUInteger const NUM_OBJECTS = 1000;
NSUInteger const INIT_BATCH_SIZE = 100;

- (void)setUp {
    [super setUp];
    helper = [[TestHelper alloc] init];
    url = [[[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]] URLByAppendingPathComponent:@"foo.sqlite"];
    [[NSFileManager defaultManager] createDirectoryAtURL:[url URLByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:NULL];
    @autoreleasepool {
        NSEntityDescription *noticeEntity = [[NSEntityDescription alloc] init];
        noticeEntity.name = @"Notice";
        NSAttributeDescription *distance = [[NSAttributeDescription alloc] init];
        distance.name = @"distance";
        distance.attributeType = NSDoubleAttributeType;
        NSAttributeDescription *time = [[NSAttributeDescription alloc] init];
        time.name = @"time";
        time.attributeType = NSDoubleAttributeType;
        noticeEntity.properties = @[distance, time];
        NSManagedObjectModel *model = [[NSManagedObjectModel alloc] init];
        model.entities = @[noticeEntity];

        NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
        [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:NULL];
        moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        moc.persistentStoreCoordinator = psc;
        for (NSUInteger count = 0; count < NUM_OBJECTS; ) {
            @autoreleasepool {
                for (NSUInteger batchCount = 0; batchCount < INIT_BATCH_SIZE && count < NUM_OBJECTS; ++batchCount, ++count) {
                    NSManagedObject *notice = [NSEntityDescription insertNewObjectForEntityForName:@"Notice" inManagedObjectContext:moc];
                    double distance = ((double)arc4random_uniform(100000)) / (arc4random_uniform(100)+1);
                    double time = distance / (arc4random_uniform(100)+1);
                    [notice setValue:@(distance) forKey:@"distance"];
                    [notice setValue:@(time) forKey:@"time"];
                }
                [moc save:NULL];
                [moc reset];
            }
        }
        [moc save:NULL];
        [moc reset];
    }
}

Cleanup after the test...

- (void)tearDown {
    [super tearDown];
    [[NSFileManager defaultManager] removeItemAtURL:[url URLByDeletingLastPathComponent] error:NULL];
}

A couple of helper methods...

- (NSArray*)executeFetchWithBatchSize:(NSUInteger)batchSize {
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Notice"];
    fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"distance" ascending:YES],
                                     [NSSortDescriptor sortDescriptorWithKey:@"time" ascending:NO]];
    fetchRequest.fetchBatchSize = batchSize;
    return [moc executeFetchRequest:fetchRequest error:NULL];
}

- (NSUInteger)numberOfFaults {
    NSUInteger numFaults = 0;
    for (NSManagedObject *object in moc.registeredObjects) {
        if (object.isFault) ++numFaults;
    }
    return numFaults;
}

A test for using the default batch size

- (void)testThatFetchRequestWitDefaultBatchSizeFetchesEverything {
    XCTAssertEqual(0, moc.registeredObjects.count);

    NSArray *fetched = [self executeFetchWithBatchSize:0];

    XCTAssertEqual(NUM_OBJECTS, moc.registeredObjects.count);
    XCTAssertEqual(NUM_OBJECTS, fetched.count);
    XCTAssertEqual(NUM_OBJECTS, [self numberOfFaults]);

    [[fetched objectAtIndex:1] valueForKey:@"distance"];
    XCTAssertEqual(NUM_OBJECTS, moc.registeredObjects.count);
    XCTAssertEqual(NUM_OBJECTS, fetched.count);
    XCTAssertEqual(NUM_OBJECTS-1, [self numberOfFaults]);
}

A test for using a non-default batch size

- (void)testThatFetchRequestWithExplicitBatchSizeOnlyFetchesTheNumberRequested {
    XCTAssertEqual(0, moc.registeredObjects.count);

    NSUInteger const BATCH_SIZE = 20;
    NSArray *fetched = [self executeFetchWithBatchSize:BATCH_SIZE];

    XCTAssertEqual(0, moc.registeredObjects.count);
    XCTAssertEqual(NUM_OBJECTS, fetched.count);
    XCTAssertEqual(0, [self numberOfFaults]);

    [[fetched objectAtIndex:1] valueForKey:@"distance"];
    XCTAssertEqual(BATCH_SIZE, moc.registeredObjects.count);
    XCTAssertEqual(NUM_OBJECTS, fetched.count);
    XCTAssertEqual(BATCH_SIZE-1, [self numberOfFaults]);
}

Thanks for your code! But i have a problem with NSFetchResultController and his NSFetchRequest, that have batch size(It's fetches all entities – Serd

Habits die hard. Instead of making that assertion, you should have taken the code I gave you and modified it slightly to test with a fetched results controller, and you would then confirm for yourself that your assertion "It's fetches all entities" is not true. For example...

Modify the helpers just a tad...

- (NSFetchRequest*)fetchRequestWithBatchSize:(NSUInteger)batchSize {
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Notice"];
    fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"distance" ascending:YES],
                                     [NSSortDescriptor sortDescriptorWithKey:@"time" ascending:NO]];
    fetchRequest.fetchBatchSize = batchSize;
    return fetchRequest;
}

- (NSArray*)executeFetchWithBatchSize:(NSUInteger)batchSize {
    return [moc executeFetchRequest:[self fetchRequestWithBatchSize:batchSize] error:NULL];
}

- (NSFetchedResultsController*)executeFetchUsingFetchedResultsControllerWithBatchSize:(NSUInteger)batchSize {
    NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:[self fetchRequestWithBatchSize:batchSize] managedObjectContext:moc sectionNameKeyPath:nil cacheName:nil];
    [frc performFetch:NULL];
    return frc;
}

And change the existing tests slightly to add tests for FRC.

- (void)testThatFetchRequestWitDefaultBatchSizeFetchesEverythingEvenWithFetchedResultsController {
    XCTAssertEqual(0, moc.registeredObjects.count);

    NSFetchedResultsController *frc = [self executeFetchUsingFetchedResultsControllerWithBatchSize:0];

    XCTAssertEqual(NUM_OBJECTS, moc.registeredObjects.count);
    XCTAssertEqual(NUM_OBJECTS, frc.fetchedObjects.count);
    XCTAssertEqual(NUM_OBJECTS, [self numberOfFaults]);

    [[frc.fetchedObjects objectAtIndex:1] valueForKey:@"distance"];
    XCTAssertEqual(NUM_OBJECTS, moc.registeredObjects.count);
    XCTAssertEqual(NUM_OBJECTS, frc.fetchedObjects.count);
    XCTAssertEqual(NUM_OBJECTS-1, [self numberOfFaults]);
}

- (void)testThatFetchRequestWithExplicitBatchSizeOnlyFetchesTheNumberRequestedEvenWithFetchedResultsController {
    XCTAssertEqual(0, moc.registeredObjects.count);

    NSUInteger const BATCH_SIZE = 20;
    NSFetchedResultsController *frc = [self executeFetchUsingFetchedResultsControllerWithBatchSize:20];
    XCTAssertEqual(moc, frc.managedObjectContext);

    XCTAssertEqual(0, moc.registeredObjects.count);
    XCTAssertEqual(NUM_OBJECTS, frc.fetchedObjects.count);
    XCTAssertEqual(0, [self numberOfFaults]);

    [[frc.fetchedObjects objectAtIndex:1] valueForKey:@"distance"];
    XCTAssertEqual(BATCH_SIZE, moc.registeredObjects.count);
    XCTAssertEqual(NUM_OBJECTS, frc.fetchedObjects.count);
    XCTAssertEqual(BATCH_SIZE-1, [self numberOfFaults]);
}
Jody Hagins
  • 27,943
  • 6
  • 58
  • 87
  • No, batch size fetch not "only 20 entities", it fetches ALL entities. Can you make a test project and test this batch size?I'm sure you will have the same issue – Serd Nov 30 '15 at 14:55
  • Thanks for your code! But i have a problem with NSFetchResultController and his NSFetchRequest, that have batch size(It's fetches all entities – Serd Dec 01 '15 at 09:25
  • A little late, but I hope it helps someone out there. Impossible to answer the question reliably; because if the 'NSManagedObjectContext' is used in a parent/child relationship, the 'fetchBatchSize' will be ignored. You will need to create an additional `NSManagedObjectContext` of type `NSMainQueueConcurrencyType` directly to the `NSPersistentStoreCoordinator`. See http://stackoverflow.com/a/11470560/4457396 for further info and also search for fetchBatchSize and parent child context. – oyalhi Mar 01 '17 at 13:04
  • Well actually, you may get the list and that seems larger than the batch size. But the objects up to the batch size are in memory and rest are faults. We could say, in database terms in memory cursor. – Abdul Yasin Nov 13 '18 at 01:05
2

The fetchBatchSize is just an efficiency measure used by Core Data to make sure it is getting as few records at one time as possible. Frankly, this is complete unnecessary in 99% of the cases and thus you can usually safely delete that line of superfluous code.

If you want to limit your fetch to a certain number of records (similar to an SQL LIMIT parameter, often used in connection with sorting), you have to set the fetchLimit property of the fetch request.

fetchRequest.fetchLimit = 20
Mundi
  • 79,884
  • 17
  • 117
  • 140
  • I understand, that i can replace fetchBatchSize with limit/offset properties, but it's not good solution. Can you test fetchBatchSize property please? – Serd Nov 30 '15 at 14:57
  • 2
    Test what? It is all explained in my answer. – Mundi Nov 30 '15 at 20:46
  • He is trying to fetch 20 records and then to load 20 more after that and to proceed that way until he reaches goal of 100. Your answer is not good. – Vladimir Sukanica Jan 14 '22 at 08:04
  • My answer summarises the documentation: https://developer.apple.com/documentation/coredata/nsfetchrequest/1506558-fetchbatchsize/. If you think it's not good, tell Apple. – Mundi Jan 15 '22 at 10:24