views:

328

answers:

3

Hi there,

I have very strange problem, when I don't understand what's going on at all, so I'm looking for explanation of it. Situation is as following:

I have a view controller with scrollview with three subviews in it. Those three subviews have method

-(void)loadContent

which loads content from database using CoreData in the background thread, creates subviews which represent loaded items and add them as own subviews calling [self addSubview: itemView]; That method is invoked as

[self performSelectorInBackground: @selector(loadContent) withObject: nil];

To load data from DB I'm using a singleton service class. Everything worked fine, but when those three views are loading their portions of data, it sometimes crashes the app.

I guessed it's because it shares one NSManagedObjectContext instance for all read operations, so I rewrote the class so it shares only NSManagedObjectModel and NSPersistentStoreCoordinator instances and creates it's own NSManagedObjectContext instance.

Suddenly, very strange thing happened. Data are loaded ok, subviews are created and added to the view hierarchy, but it get never displayed on the screen. When I switch back to the old singleton service class (sharing one managedObjectContext), it works again like a charm! (but with risk of crashing the app, though).

I absolutely don't get the point how loading data from DB is related to displaying items on the screen. More on that - when subviews are created and added to the view hierarchy, why the hell it don't get displayed?

The source looks like this:

- (void) loadContent {

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

NSArray *results = [(WLDataService *)[WLDataService service] loadItemsForGDView];

NSUInteger channelPosition = 0;
CGFloat position = 0.0;
CGFloat minuteWidth = ((self.superview.frame.size.width / 2.0) / 60.0);

for(Item *it in results) {


 /// On following lines size and position of the view is computed according to item setup - skipping here...

 /// Create item; it's simple subclass of UIView class
 WLGDItemView *item = [[WLGDItemView alloc] init];

 /// Variables used here are declared above when size and position is computed
 item.frame = CGRectMake(itemX, itemY, itemWidth, itemHeight);

 [self performSelectorOnMainThread: @selector(addSubview:) withObject: item waitUntilDone: NO];

            /// This is just helper macro to release things
             WL_RELEASE_SAFELY(item);
}

[pool drain];
}

The basic service class (non-singleton one) implementation is as follows (just interesting parts):

#import "WLLocalService.h"

static NSPersistentStoreCoordinator *sharedPSC = nil;
static NSManagedObjectModel *sharedMOM = nil;

@implementation WLLocalService

@synthesize managedObjectContext;

/// This is here for backward compatibility reasons
+ (WLLocalService *) service {

return [[[self alloc] init] autorelease];

 }

#pragma mark -
#pragma mark Core Data stack

- (NSManagedObjectContext *) managedObjectContext {

if (managedObjectContext == nil) {

 NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];

 if (coordinator != nil) {
  managedObjectContext = [[NSManagedObjectContext alloc] init];
  [managedObjectContext setPersistentStoreCoordinator: coordinator];
 }

 [managedObjectContext setUndoManager: nil];
 [managedObjectContext setMergePolicy: NSMergeByPropertyStoreTrumpMergePolicy];
}

return managedObjectContext;
}

- (NSManagedObjectModel *) managedObjectModel {

if(sharedMOM == nil) {
 sharedMOM = [[NSManagedObjectModel mergedModelFromBundles: nil] retain];
}

return sharedMOM; 
}

- (NSPersistentStoreCoordinator *) persistentStoreCoordinator {

if(sharedPSC == nil) {

 NSURL *storeUrl = [self dataStorePath];

 NSError *error = nil;
 sharedPSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];

 if (![sharedPSC addPersistentStoreWithType: NSSQLiteStoreType configuration: nil URL: storeUrl options: nil error: &error]) {
  WLLOG(@"%@: %@", error, [error userInfo]);
 }
}

return sharedPSC;
}

#pragma mark -
#pragma mark Path to data store file
- (NSURL *) dataStorePath {
return [NSURL fileURLWithPath: [WL_DOCUMENTS_DIR() stringByAppendingPathComponent: @"/DB.sqlite"]];
}

- (void)dealloc {

WL_RELEASE_SAFELY(managedObjectModel);

[super dealloc];
}

@end

I'd really love to know what's going on here and why it behaves so strange (and - of course - why it does not work, in particular). Can anybody explain that?

thanks to all

+1  A: 

Have you read Multi Threading with Core-Data twice?

St3fan
Yes, I read that hundered times. That's why I tried to modify my singleton class, making it not singleton and using separate managed object context for each instance. I'm not sending managed object instances between threads, I'm just creating graphic representation according its parameters. And I'm confused why UIView objects are not being displayed!
Matthes
You missed the part that says 'For completely concurrent operations you need a different coordinator for each thread.' then. Your non-singleton does behave like a singleton since it has global variables to keep track of singleton instances of the PSC and the MOM. Since you are using that code from multiple threads, there might be a concurrency issue. There is certainly a potential issue when initializing them since you don't synchroize access to them.
St3fan
So, I modified my service class, so each instance keeps it's own instance of coordinator and managed object context, both accessor methods are synchronized. But no change - content of the view is still not displayed. I do not realize how is that possible and what is the relation between loading data from DB and displaying something completely else. Argh!!!
Matthes
A: 

You realize that [WLDataService service] does not actually return a singleton? It creates a new instance every time. So you are effectively working with multiple instances of the Core Data components.

What about:

static WLDataService* gSharedService = NULL;

@implementation WLDataService

+ (id) service
{
    @synchronized (self) {
        if (gSharedService == NULL) {
            gSharedService = [[self alloc] init];
        }
    }
    return gSharedService;
}

@end

That will create the same instance every time. You will also want to make your managedObjectContext, managedObjectModel and persistentStoreCoordinator methods thread safe by using a @synchronized block. Otherwise there is a change that multiple threads will initialize those at the same time, leading to unexpected behaviour.

St3fan
I told I changed it the way to not to be singleton, so it's exactly what I want - new instance every time with separate managed object context for each instance and shared persistent store coordinator and managed object model to avoid thread concurrent access to one managed context. And that lead to situation I described - not drawing content on the screen, although it was created and attached to the view hierarchy.
Matthes
A: 

First, do not load or construct UI elements on a background thread. The UI (whether on the desktop or on the iPhone) is single threaded and manipulating it on multiple threads is a very bad idea.

Second, data that you load into one context will not be immediately visible in another context. This is what is causing part of your problem.

The solution is to move all your UI code to the main thread and warm up the Core Data cache on a background thread. This means to load the data on a background thread (into a separate cache) to load it into the NSPersistentStoreCoordinator cache. Once that is complete your main thread can access that data very quickly because it is now in memory.

Marcus S. Zarra