Reputation: 401
I am trying to make app, that need to get N random objects from CoreData.
The problem is that the database size is much larger than the number of objects that I want to get. Therefore, it would be desirable to have a method that does not affect all the records in the database.
Is it possible to implement this without adding extra fields to the database (eg, id)?
I want to receive different responses to requests, so option "pre-sorted using some random function" is not working.
Upvotes: 1
Views: 819
Reputation: 1511
Swift 5 version
// First count the records
let countRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Dictionary");
do {
let count = try managedObjectContext.count(for: countRequest)
// Then run the query again to choose a random row
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Dictionary");
request.fetchLimit = 5
request.fetchOffset = Int(arc4random_uniform(UInt32(count)))
do {
// Here's your results
let results = try managedObjectContext.fetch(request) as! [Dictionary]
}
catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
Upvotes: 1
Reputation: 80271
The problem with fetching n
objects one by one could be performance. Depending on how large n
is, this might not scale.
Instead, you could take advantage of the fact that Core Data is pretty efficient when handling large number of objects. You could fetch tens of thousands of objects without too large a memory footprint due to a mechanism called "faulting".
Thus, i would suggest that you fetch all objects and simply pick some out from the result.
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Response"];
NSArray *all = [managedObjectContext executeFetchRequest:request error:nil];
NSMutableArray *pickedAnswers = [NSMutableArray array];
int remaining = 10;
if (all.count < remaining) { /* abort */ }
while (remaining > 0) {
Response *response = all[arc4random_uniform(all.count)];
if (![pickedAnswers containsObject:response]) {
[pickedAnswers addObject:response];
remaining--;
}
}
Note that another elegant answer would be to first shuffle the array (as shown here and then pick the first n
elements). That would eliminate unnecessary runs through the while
loop.
Upvotes: 3
Reputation: 2027
Count all the records first:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"<your entity name>"];
NSUInteger count = [context countForFetchRequest:request error:NULL];
Use fetchOffset
:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"<your entity name>"];
request.fetchLimit = 1;
request.fetchOffset = arc4random_uniform(count);
NSArray *result = [context executeFetchRequest:request error:NULL];
Repeat N times to get desired number of objects.
Upvotes: 5