I have a tableview in my app that contains a NSFetchedResultsController to load in some CoreData objects.
As the table builds in cellForRowAtIndexPath:
, for each cell I must do a fetch to get some other info from another object.
The table is filled with UserTasks, and I must get some info from a UserSite (UserTask contains a siteID attribute)
I am getting the UserSite info in a background thread, and using a temporary context. It works fine, but it still wants to lag the UI a bit when scrolling.
Site *site = [_scannedSites objectForKey:task.siteID];
if(!site)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
AppDelegate *ad = [AppDelegate sharedAppDelegate];
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
temporaryContext.persistentStoreCoordinator = ad.persistentStoreCoordinator;
Site *site2 = [task getSiteWithContext:temporaryContext];
if(site2)
{
[ad.managedObjectContext performBlock:^{
Site *mainContextObject = (Site *)[ad.managedObjectContext objectWithID:site2.objectID];
[_scannedSites mainContextObject forKey:task.siteID];
}];
dispatch_async(dispatch_get_main_queue(), ^{
Site *newSite = [_scannedSites objectForKey:task.siteID];
cell.lblCustName.text = newSite.siteName;
cell.lblAddr.text = [NSString stringWithFormat:@"%@ %@, %@", newSite.siteAddressLine1, newSite.siteCity, newSite.siteState];
cell.lblPhone.text = [self formatPhoneNum:newSite.phone];
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
cell.lblCustName.text = @"";
cell.lblAddr.text = @"";
cell.lblPhone.text = @"";
});
}
});
}
else
{
cell.lblCustName.text = site.siteName;
cell.lblAddr.text = [NSString stringWithFormat:@"%@ %@, %@", site.siteAddressLine1, site.siteCity, site.siteState];
cell.lblPhone.text = [self formatPhoneNum:site.phone];
}
As you can see, if you dont already have the UserSite info for a task in _scannedSites
, a background thread gets kicked off which gets the UserSite for that task, stores it, and then on the main thread fills in the details.
Like I said there is a pretty annoying lag when scrolling... which I hoped to avoid by doing the work in the background.
Am I going about this the wrong way?
Thanks, any advice is appreciated.
EDIT
I created a relationship in CoreData and I am now using that in cellForRowAtIndexPath
. If it does not exist yet, I create it. This is working much better.
Site *site = task.site;
if(!site)
{
AppDelegate *ad = [AppDelegate sharedAppDelegate];
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.persistentStoreCoordinator = ad.persistentStoreCoordinator;
[temporaryContext performBlock:^{
Site *tempContextSite = [task getSiteWithContext:temporaryContext];
[ad.managedObjectContext performBlock:^{
Site *mainManagedObject = (Site *)[ad.managedObjectContext objectWithID:tempContextSite.objectID];
task.site = mainManagedObject;
NSError *error;
if (![temporaryContext save:&error])
{
}
[ad.managedObjectContext performBlock:^{
NSError *e = nil;
if (![ad.managedObjectContext save:&e])
{
}
dispatch_async(dispatch_get_main_queue(), ^{
cell.lblCustName.text = mainManagedObject.siteName;
cell.lblAddr.text = [NSString stringWithFormat:@"%@ %@, %@", mainManagedObject.siteAddressLine1, mainManagedObject.siteCity, mainManagedObject.siteState];
cell.lblPhone.text = [self formatPhoneNum:mainManagedObject.phone];
});
}];
}];
}];
}
else
{
cell.lblCustName.text = site.siteName;
cell.lblAddr.text = [NSString stringWithFormat:@"%@ %@, %@", site.siteAddressLine1, site.siteCity, site.siteState];
cell.lblPhone.text = [self formatPhoneNum:site.phone];
}
If UserTask
relates to UserSite
, the usual Core Data approach would be to create a relationship between the two and then use that relationship at run time. So, UserTask
would have a property named site
, and you'd just ask a specific instance for the value of that property. An ID attribute might still exist but would only be used when syncing with some external data store (like a server API).
Storing IDs and looking up objects like this is a fundamentally awkward approach that's pretty much designed to do a lot of unnecessary work at run time. It avoids all of the conveniences that Core Data tries to provide, doing things the hard way instead. Doing this work while the table is scrolling is also about the worst possible time, because it's when a performance issue will be most noticeable.
If you must do it this way for some reason, you could optimize things by looking up all of the UserSite
instances in advance instead of while the table is scrolling. If you know all of the UserTask
instances, go get all the sites in one call when the view loads.