GCD safe NSOutlineView

Discussion in 'Mac Programming' started by MrFusion, Dec 31, 2010.

  1. MrFusion, Dec 31, 2010
    Last edited: Dec 31, 2010

    MrFusion macrumors 6502a

    Joined:
    Jun 8, 2005
    Location:
    West-Europe
    #1
    Hi everyone

    I am working with GCD, an NSOutlineView and coredata this winterbreak. I am using GCD to load data from disk to store it into a coredata model. Since this takes a long time, I am using GCD. The problem though are the NSAssertion messages from NSOutlineView.

    *** Assertion failure in -[MyOutlineView _expandItemEntry:expandChildren:startLevel:], /SourceCache/AppKit/AppKit-1038.35/TableView.subproj/NSOutlineView.m:969
    (null) should not be expanded already!

    The cause, I think, is either:
    NSOutlineView once in a while updates its view while GCD is still loading data.
    Or:
    My GCD enabled code is modifying the datasource outside of the main thread.

    Is there any way to keep the GCD code and have it add data to coredata without NSOutlineView complaining/crashing? The NSOutlineview is already disabled to prevent the user from changing the selection or the data while adding data.

    This code represents the basic idea I want implement.
    I appreciate all your input and suggestions.


    Code:
    #import "DataTree.h" //subclass of NSTreeController
    #import "DataAdding.h" //class that does all the heavy lifting, does not interact directly with coredata
    
    @implementation DataTree
    -(IBAction) test:(id)sender
    {
    	[self addManagedObject:@"MyTreeNode"];
    	DataAdding *da = [[DataAdding alloc] init];
    	
    	//coredata is accessed only via blocks such as these
    	void (^processingBlock) (NSURL *, id, NSError *) = ^(NSURL *url, id importedData, NSError *err)
    	{
                    //data has been obtained, now add it to the document (i.e. coredata)
    		id newTreenode = [self treeNode:@"MyTreeNode"
    					         withData:importedData 
    						 fromURL:nil];
    		
    	};
    	
    	[da runConcurrentForBlock:processingBlock];
    	
    	[da release];
    }
    
    -(NSTreeNode *) treeNode:(NSString *) entityName 
    			withData:(id) data
    		        fromURL:(NSURL *) url
    {
    	/**
    	 entityName and url (filelocation) are not an intrinsic part of the data and should therefore be handled separately 
    	 */
    	
    	__block NSTreeNode *newTreeNode = nil;
    	id newDataNode = nil;
    	
    	@try  //Validity of entityName is not guaranteed. Furthermore processData" may or may not be implemented.
    	{
    		//create new datanode
    		newDataNode = [NSEntityDescription insertNewObjectForEntityForName:entityName
    													inManagedObjectContext:[self managedObjectContext]];  //Add a new node into the tree.
    		//process data
    //		[newDataNode processData:data]; //not used in this test example
    
    		[newDataNode setValue:data //data in this test example is a NSString 
    					   forKey:@"title"];
    		
    		//locate corresponding rootNode
    		void (^identify_loop)(id, NSUInteger, BOOL *) = ^(id rootNode, NSUInteger index, BOOL *stop) 
    		{	
    			if ([[rootNode representedObject] isEqual:newDataNode])
    			{	
    				newTreeNode = rootNode;
    				*stop = YES;
    			}
    		};
    		
    		[[[self arrangedObjects] childNodes] enumerateObjectsWithOptions:(NSEnumerationConcurrent & NSEnumerationReverse)
    															  usingBlock:identify_loop];
    		
    	}
    	@catch (NSException * e) 
    	{
    		NSLog(@"Caught exception: Invalid entityName \"%@\" or does not implement \"processData\"",entityName);
    		if (newDataNode)
    			[[self managedObjectContext] deleteObject:newDataNode];
    		newTreeNode = nil;
    	}
    	return newTreeNode;
    }
    @end
    
    
    
    Code:
    
    #import "DataAdding.h"
    
    
    @implementation DataAdding
    
    -(void) runConcurrentForBlock:(void (^)(NSURL *,id, NSError *)) processingBlock
    {
    	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{ //don't wait for me
                    //simulate a long process: many objects that take some processing time
    		for (int i =0; i<55; i++) 
    		{
    			sleep(1);  //data reading/processing which takes a long time is handled on a global queue. 
    	               [B] dispatch_async(dispatch_get_main_queue(),^{ //finishing up, don't wait for me  => Part of edit[/B]
    			      processingBlock(nil,[NSString stringWithFormat:@"%i",i],nil);	//adding results to the datasource is done on the main queue
    	                 [B]});  => Part of edit[/B]
    		}
    	});	
    }
    
    @end
    
    

    Edit:
    If the data is added to coredata or the datasource in general on the main queue, then everything works fine (so far at least).
    Inspired by this link
     
  2. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #2
    I don't know how thread safe Core Data is, but your accessing of the arrangedObjects in the queue is most likely not thread safe. You should process that part on the main queue.
     

Share This Page