2

I am trying to create background threads for fetching Core Data in order to populate an NSOutlineView. The operation can take a few seconds so I was wanting to put the operation in a background thread and display the UI to the user as shown below. As the searches complete for each section I am calling a function on the main thread to reload the data for that section. This seemed to be working OK but I have had one instance where an exception was thrown during the Core Data fetch operation. The user can't really do anything else so i figured it was probably OK to do Core Data calls on a background thread as long as nothing else can do so at the same time. Subsequently I have used NSLock's around the code doing updates to the array that holds the data for display. I can't seem to cause any exceptions now. Is this a safe way to retrieve data from a managedObjectContext ?

enter image description here

The basic approach I have used is illustrated in the code snippets below and is as follows: 1. In initWithNibName populate the initial data for display 2. In awakeFromNib setup the outlineView and kick of the background thread to fetch the first set of data 3. Run the background thread and block while updating shared objects 4. When the thread is done trigger execution of a function on the main thread to a) reload the outlineView (wish I knew how to only reload the section of NSOutlineView), and b) kick of the background thread for the next section's fetch.

In general this seems to work OK and seems to create a better visual experience for the user than simply running it all on the main thread and watching the coloured umbrella spinning for a few seconds...

Can anyone confirm whether this is a robust approach - I am not sure I fully understand what NSLock does and I suspect there may be some holes in this approach but can't seem to break it.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Initialization code here.

        _reminderCategories = [NSArray arrayWithObjects:REMINDERS_PAST, REMINDERS_TODAY, REMINDERS_THIS_WEEK, REMINDERS_THIS_MONTH, REMINDERS_THIS_YEAR, nil];
        _reminderItems = [NSMutableDictionary new];

        [_reminderItems setObject:[NSArray arrayWithObjects:@"Searching...", nil] forKey:REMINDERS_PAST];
        [_reminderItems setObject:[NSArray arrayWithObjects:@"Searching...", nil] forKey:REMINDERS_TODAY];
        [_reminderItems setObject:[NSArray arrayWithObjects:@"Searching...", nil] forKey:REMINDERS_THIS_WEEK];
        [_reminderItems setObject:[NSArray arrayWithObjects:@"Searching...", nil] forKey:REMINDERS_THIS_MONTH];
        [_reminderItems setObject:[NSArray arrayWithObjects:@"Searching...", nil] forKey:REMINDERS_THIS_YEAR];
    }

    return self;
}


- (void)awakeFromNib;
{
    // Make sure we only do this once
    if (!_awake) {
        _awake = YES;
        LOG(@"awakeFromNib called !");
        //set the start menu option
        [_sidebarOutlineView sizeLastColumnToFit];
        [_sidebarOutlineView reloadData];
        [_sidebarOutlineView setFloatsGroupRows:NO];

        // Expand all the root items; disable the expansion animation that normally happens
        [NSAnimationContext beginGrouping];
        [[NSAnimationContext currentContext] setDuration:0];
        [_sidebarOutlineView expandItem:nil expandChildren:YES];
        [NSAnimationContext endGrouping];

        if (!_searched) {
            _searched=YES;
            [self getPastRemindersUsingThread];
        }
    }
}

- (void)getPastRemindersUsingThread
{   //LOG(@"getPastRemindersUsingThread called");
    _loading = YES;
    [self performSelectorInBackground:@selector(threadGetPastReminders) withObject:nil];
}
- (void)threadGetPastReminders
{
    //LOG(@"threadGetReminders called");
    assert( ! [NSThread isMainThread] );
    [NSThread sleepForTimeInterval:2];  // Delays for testing
    [self makeMenus];
    [self performSelectorOnMainThread:@selector(pastRemindersDone) withObject:nil waitUntilDone:NO];
}
- (void)pastRemindersDone {
    //LOG(@"pastRemindersDone called");
    [_sidebarOutlineView reloadData];
    [self getTodaysRemindersUsingThread];
}
- (void)getTodaysRemindersUsingThread
{   //LOG(@"getTodaysRemindersUsingThread called");
    _loading = YES;
    [self performSelectorInBackground:@selector(threadGetReminders) withObject:nil];
}
- (void)threadGetReminders
{
    //LOG(@"threadGetReminders called");
    assert( ! [NSThread isMainThread] );
    [self makeTodaysMenusX];
    [self performSelectorOnMainThread:@selector(todaysRemindersDone) withObject:nil waitUntilDone:NO];
}
- (void)todaysRemindersDone {
    //LOG(@"todaysRemindersDone called");
    [_sidebarOutlineView reloadData];
    [self getThisWeeksRemindersUsingThread];
}

// etc…

-(void)makeMenus {
    //LOG(@"makeMenus called");

    // Make sure nothing else is going on while we execute this code
    NSLock *theLock = [[NSLock alloc] init];
    if ([theLock tryLock]) {
        [self makeDates];
        [self getAllReminders];

        [self makePastMenus];
    }
    [theLock unlock];
}
- (void)makePastMenus {
    //LOG(@"makePastMenus called");
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"nextReminderDate < %@",_todayStartDate];
    [self makeMenu:REMINDERS_PAST predicate:predicate];
}
- (void)makeTodaysMenus {…}
- (void)makeThisWeeksMenus {…}
- (void)makeThisYearsMenus {…}


// Use this to get the subset that fall within the specified dates (in the predicate)
// nextReminderDate is calculated, and not an attribute stored in the database
- (void)makeMenu:(NSString*)menu predicate:(NSPredicate*)predicate  {
    LOG(@"makeMenuX called");
    NSLock *theLock = [[NSLock alloc] init];
    if ([theLock tryLock]) {
        if (_yearlyReminders) {
            NSMutableArray *reminders = [[NSMutableArray alloc] init];
            [reminders addObjectsFromArray:[_yearlyReminders filteredArrayUsingPredicate:predicate]];
            NSSortDescriptor *indexSort = [[NSSortDescriptor alloc] initWithKey:@"nextReminderDate" ascending:YES];
            NSArray *sorters = [NSArray arrayWithObject:indexSort]; indexSort = nil;
            [_reminderItems setObject:[reminders sortedArrayUsingDescriptors:sorters] forKey:menu];
        } else {
            LOG(@" error utilities is nil!");
        }
    }
    [theLock unlock];
    LOG(@"makePastYearsMenus finished");
}

// Get all the reminders from the Core Data store
// _utilities getData is a helper function that  performs the Core Data fetch and returns an array of NSManagedObjects 
- (void)getAllReminders {
    //LOG(@"getYearlyReminders called");
    NSLock *theLock = [[NSLock alloc] init];
    if ([theLock tryLock]) {
        if (_utilities) {
            NSPredicate *predicate = [...];
            NSMutableArray *reminders = [[NSMutableArray alloc] init];
            [reminders addObjectsFromArray:[_utilities getData:@"Entity1" sortKey:@"reminderDate" predicate:predicate]];
            [reminders addObjectsFromArray:[_utilities getData:@"Entity2" sortKey:@"reminderDate" predicate:predicate]];
            [reminders addObjectsFromArray:[_utilities getData:@"Entity3" sortKey:@"reminderDate" predicate:predicate]];
            [reminders addObjectsFromArray:[_utilities getData:@"Entity4" sortKey:@"reminderDate" predicate:predicate]];
            NSSortDescriptor *indexSort = [[NSSortDescriptor alloc] initWithKey:@"nextReminderDate" ascending:YES];
            NSArray *sorters = [NSArray arrayWithObject:indexSort]; indexSort = nil;

            _yearlyReminders = [[NSArray alloc] initWithArray:[reminders sortedArrayUsingDescriptors:sorters]];
        } else {
            LOG(@" error utilities is nil!");
        }
    }
    [theLock unlock];
}
Duncan Groenewald
  • 8,496
  • 6
  • 41
  • 76
  • I also don't know much about NSLock - but wouldn't it need to be a shared object? If you alloc/init a new one everywhere I don't see how that will block any execution. As for your `NSManagedObjectContext` question - Do not cross threads/queues with those. Fetch via objectID. – Michael Kernahan Sep 17 '13 at 13:59

0 Answers0