Question

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];
    }
Was it helpful?

Solution

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.

OTHER TIPS

It is a bad idea to send of asynchronous tasks in cellForRowAtIndexPath:. If the user scrolls there are going to be a whole bunch of threads created which are maybe not even necessary.

It would be much better to have a background process that fetches the information you want and then notifies the UI to update itself if needed. This is pretty standard stuff, you will find many examples for solid implementations easily.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top