6

I run my program that creates Core Data content that is displayed in a NSOutlineView using NSTreeController. The second time I run my program I want to clean the content of my NSTreeController and I run the method pasted below. The method either hangs for a long time (600 seconds) before it finishes or it crashes. If I have few entities (500-1000) in my NStreeController it takes much less time compared to if I have a lot (200,000) entities to pass this method, if it passes at all. What I need to know is if there is a better way to clear/refresh/reset the content of my NStreeController to clear my NSoutlineView before I re-run my program and fill up the NStreeController again. Specifically, I would like my NSOutlineView to respond quickly to changes to the contents of my NSTreeController, and I need the content of my Core Data driven NSTreeController to be able to be reset.

-(void) cleanSDRDFileObjects
{
    __weak __typeof__(self) weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{     
        [weakSelf.outlineView collapseItem:nil collapseChildren:YES];
        [weakSelf.coreDataController._coreDataHelper.context  performBlockAndWait:^{

            NSEntityDescription *entityDescription = [NSEntityDescription
                                                  entityForName:@"SDRDFileObject"                                        inManagedObjectContext:weakSelf.coreDataController._coreDataHelper.context];

            NSFetchRequest *request = [[NSFetchRequest alloc] init];
            [request setEntity:entityDescription];

            NSArray * result = [weakSelf.coreDataController._coreDataHelper.context  executeFetchRequest:request error:nil];
            for (id fileobject in result){
                [weakSelf.coreDataController._coreDataHelper.context deleteObject:fileobject];
            }
            [weakSelf.coreDataController._coreDataHelper.context processPendingChanges];

            NSLog(@"Finished deleting all objects");
        }];
    });
 }

The managedobjectcontext (context) is run as type NSMainQueueConcurrencyType and the method is run on the main thread. Suggestions for improvements, or useful examples for the combination of reset/refreshing NSOutlineView + Core Data would be greatly appreciated. Thanks. Cheers, Trond

In response to @TomHarringtons question I took a picture of my Time Profiler. I really dont understand why it hangs on this method, however, after commenting this methods out (```processPendingChanges```), it still hangs and takes forever to finish (6 minutes). It seems the process gets stuck on the main thread and cant continue.

enter image description here

When I rerun the application, with processPendingChanges commented out its still hanging.

enter image description here

Update

I believe I solved this but I am slightly uncertain as to why this worked. It seems that my first method went into an indefinite loop that did not release its objects. The following simple solution worked:

     __weak __typeof__(self) weakSelf = self;
     dispatch_sync(dispatch_get_main_queue(), ^{
         [weakSelf.coreDataController._coreDataHelper.context reset];
      }); 

I was certain that to properly empty a managed object context I would have to delete each entity individually. The reset function seems pretty brute force and does it actually clean up memory and make sure everything is okay? If anyone wants to shed some light on this that would be appreciated.

Chris
  • 7,270
  • 19
  • 66
  • 110
Trond Kristiansen
  • 2,379
  • 23
  • 48
  • will/didChangeValueForKey is generally called from accessors of the `self` instance. Why would you call them from this method? And NSTreeController doesn't even have a keyPath "children." It has a keyPath "childrenKeyPath". Regardless of all that-- are you looking for `rearrangeObjects` ?? – stevesliva Dec 06 '14 at 05:55
  • [`rearrangeObjects`](http://stackoverflow.com/questions/23773827/how-can-i-determine-if-apple-methods-are-asynchronous/) will sometimes schedule the GUI refresh in the main thread queue, so it may well have something to do with your issue. Nonetheless, your use of the KVC methods is frightening, and you should study up on that before ever using will/didChangeValueForKey. I also wonder when you push the changes from the background context to the mainThread/GUI context. – stevesliva Dec 06 '14 at 05:59
  • @stevesliva Thanks for your comments. I realize I copied and pasted my code when I was randomly testing KVO (Ill study). I have been using KVO on arrangedObjects (without success). I see now that my outlineView delegate is crashing as I am deleting entities, especially when I delete an entity before deleting its children and the outlineview tries to update a deallocated item. If you have suggestions for best practice in resetting a tableview or outlineview driven by Core Data that would be great. I have googled this problem but can`t find good examples on how to reset a Core Data driven view. – Trond Kristiansen Dec 07 '14 at 00:09
  • If it's taking that long to run, it should be easy to narrow down the cause by running the app in Instruments. – Tom Harrington Dec 09 '14 at 00:54
  • @TomHarrington: Thanks I added a profiling picture. When I edit out the ```processPendingChanges``` It still hangs. – Trond Kristiansen Dec 09 '14 at 01:06
  • 1
    Regarding `reset`, yes, it gives up memory, especially in an `autoreleasepool{}` enclosure. – stevesliva Jan 04 '15 at 20:58

1 Answers1

1

Looking at this again, you fetched all objects of a type in performBlockAndWait -- this blocks the main thread because you have mainQueueConcurrency and you used the andWait version of performBlock.

You then delete each object one-by-one. These objects are in a tree data structure with a outlineview attached (see the KVO messages in the stack trace). These objects have to-many relationships that need to be maintained by core data, hell, you could even have a cascading delete rule. (see propagateDelete and maintainInverseRelationship in the stack trace) In any event, you start requesting that both the data source and the view start doing a lot of work, on the main thread. You could try using a child MOC with privateQueueConcurrency if you wanted to iterate all objects in the background.

But, like the comments indicated:

NSManagedObjectContext's reset most definitely frees up memory, and it's fine for what you want to do here: blow everything away.

It begs the question why you load the model from the store on disk in the first place, though.

If you want Core Data, but not persistence between the times you run the program, you can initialize the persistentStoreCoordinator with a store of NSInMemoryStoreType rather than pointing it to a file URL.

stevesliva
  • 5,351
  • 1
  • 16
  • 39
  • I did not know about the NSInMemoryStoreType and I do only use Core Data as "In Memory" (not persistence) so this is a great tip! Thank you! – Trond Kristiansen Feb 19 '15 at 23:53
  • I recently heard it suggested for testing purposes... you could write unit tests that create in-memory stores and throw 'em away without coming up with a location on disk for your testing playground. But in this case, it fits the bill functionally as well. – stevesliva Feb 20 '15 at 01:32