views:

50

answers:

1

Background

  • I've got an NSOutlineView that shows TrainingGroup entities.

  • Each TrainingGroup represents a folder on the local machine.

  • The NSOutlineView is bound to an NSTreeController with a fetch predicate of IsTrained == 0

  • Each TrainingGroup can be assigned to a project.

  • Each TrainingGroup has many TrainingEntries that show a time worked on that file.

  • When the TrainingGroup is assigned to a project, the IsTrained is set to YES.

  • On assign to a project, all descendants are also assigned to that project and their IsTrained property is set to YES too.

  • Project column is bound to projectTopLevel property.

Example

The whole tree looks like this:

Name                       Project              IsTrained
Users                      nil                  NO
  John                     nil                  NO              
    Documents              nil                  NO
      Acme Project         Acme Project         YES
        Proposal.doc       Acme Project         YES
          12:32-12:33      Acme Project         YES
          13:11-13:33      Acme Project         YES
          ... etc
        Budget.xls         Acme Project         YES
      Big Co Project       Big Co Project       YES
        Deadlines.txt      Big Co Project       YES
        Spec.doc           Big Co Project       YES
      New Project          nil                  NO
        StartingUp.doc     nil                  NO
      Personal Stuff       Personal             YES
        MyTreehouse.doc    Personal             YES
    Movies                 nil                  NO
      Aliens.mov           nil                  NO
      StepMom.mov          nil                  NO

And the NSOutlineView would only see this:

Users                      nil                  NO
  John                     nil                  NO              
    Documents              nil                  NO
      New Project          nil                  NO
        StartingUp.doc     nil                  NO
    Movies                 nil                  NO
      Aliens.mov           nil                  NO
      StepMom.mov          nil                  NO

If you assigned Movies to Personal, the view would now look like this:

Users                      nil                  NO
  John                     nil                  NO              
    Documents              nil                  NO
      New Project          nil                  NO
        StartingUp.doc     nil                  NO

Code

TrainingGroup.m

-(void)setProjectTopLevel:(JGProject *)projectToAssign {
    [self setProjectForSelf:projectToAssign];
    [self setProjectForChildren:projectToAssign];
}

-(void)setProjectForSelf:(JGProject *)projectToAssign {
    [self setProject:projectToAssign];
}

-(void)setProjectForChildren:(JGProject *)projectToAssign {
    for (TrainingGroup *thisTrainingGroup in [self descendants]) {
        [thisTrainingGroup setProject:projectToAssign];
        if(projectToAssign != nil) {
            [thisTrainingGroup setIsTrainedValue:YES];
        } else {
            [thisTrainingGroup setIsTrainedValue:NO];
        }
        // Other code updating rules.
    }
}

-(JGProject *)projectTopLevel {
    return [self project];
}

-(NSSet *)untrainedChildren {
    // Code that loops through all children returning those
    // whose isTrained is NO. Omitted for brevity.
}

The Problem

As you can see above, I'm running all the project assignment code on the main thread currently.

When there are hundreds of time entries under each folder, my app becomes unresponsive.

Possible Solutions

1 Modal progress bar

The Approach

  • Run project assignment on a background thread in separate context.
  • Use standard Core Data merge into main context when finished.
  • A modal sheet blocks any further activity until the project assignment has finished.

The Good

  • The user gets immediate feedback on what's happening.
  • The app remains responsive.

The Bad

  • User can't do anything until the current assignment has finished.

2 Non Modal Spinner

The Approach

  • Run project assignment on a background thread in separate context.
  • Use standard Core Data merge into main context when finished.
  • Show progress spinner alongside training group, indicating it's busy.
  • On finish assigning, the training group disappears from the view.

The Good

  • The user can do other stuff whilst their last action is being processed
  • The app remains responsive. Kinda. See below.

The Bad

  • In tests, I've seen a freeze of up to 3 seconds when the background context is merged into the main context.
  • The view could update in the middle of the user doing something else, which might be annoying.
  • Undo would be hard to implement.

3 Hide

The Approach

  • Above, except the training group is removed on assign, and is set to be "In Progress" until assign has finished.

Good and Bad

  • Same as above, except the ordering of the training groups would remain predicatable.
  • Still large freeze on merge back into main context.

4 Improve performance

The Approach

  • Keep the code as it is, running on the main thread.
  • Improve performance so even with thousands of entries, the view only freezes for half a second max

The Good

  • App remains responsive.
  • Undo remains easy.
  • Architecture remains simple.

The Bad

  • As I understand it, against Apple's recommendations - intensive processing should not be done on the main thread
  • Can I get the performance good enough? Unknown.

My Questions

As far as I can see, none of the options above are ideal.

1. Which is the best option?

2. Are there any other options?

3. What could I improve about my approach?

+1  A: 
  1. I would update on a background thread (No. 2)
  2. You could always disable user input on sections of your window, and display a loading message.
  3. Simple to say - go through all your code, and make sure you are making the least number of calls required, and that you do not call any unnecessary functions. You could also run some long lasting operations on separate threads, and then get notified when the action finishes, so you can process other things while the operation goes on.
jrtc27