1

Iam trying out a chat based app. I have setup a singleton coredata manager as follows

#import "CGSharedCoreData.h"
CGSharedCoreData *_cd;


@implementation CGSharedCoreData

@synthesize managedObjectModel = managedObjectModel_;
@synthesize managedObjectContext = managedObjectContext_;
@synthesize persistentStoreCoordinator = persistentStoreCoordinator_;


+ (CGSharedCoreData *)sharedCoreData{
    static CGSharedCoreData *_cd = nil;
    static dispatch_once_t onceCoreDataShared;
    dispatch_once(&onceCoreDataShared, ^{
        _cd = [[CGSharedCoreData alloc] init];
    });
    return _cd;
}


+ (void)saveContext:(NSManagedObjectContext*)c{

    @try {


        if (c.persistentStoreCoordinator.persistentStores.count == 0)
        {
            // This is the case where the persistent store is cleared during a logout.
            CGLog(@"saveContext: PersistentStoreCoordinator is deallocated.");
            return;
        }


        // Register context with the notification center
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        CGSharedCoreData *sharedCoreData = [CGSharedCoreData sharedCoreData];

        [nc addObserver:sharedCoreData
               selector:@selector(mergeChanges:)
                   name:NSManagedObjectContextDidSaveNotification
                 object:c];


        NSError *error = nil;
        if (c != nil) {
            CGLog(@"thread  &&*&(*(* %d",[NSThread isMainThread]);
            if ([c hasChanges] && ![c save:&error]) {
                CGLog(@"Unresolved error %@, %@", error, [error userInfo]);
            }
        }

        [nc removeObserver:sharedCoreData name:NSManagedObjectContextDidSaveNotification
                    object:c];

    }

    @catch (NSException *exception) {
        CGLog(@"***** Unresolved CoreData exception %@", [exception description]);
    }

}


+ (dispatch_queue_t) backgroundSaveQueue
{
    static dispatch_queue_t coredata_background_save_queue;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        coredata_background_save_queue = dispatch_queue_create("com.shashank.coredata.backgroundsaves", NULL);
    });

    return coredata_background_save_queue;
}


+ (void)performInTheBackground:(void (^)(NSManagedObjectContext *blockContext))bgBlock {

    dispatch_async([CGSharedCoreData backgroundSaveQueue],
    ^{
        NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] init];
        [newContext setPersistentStoreCoordinator:CG_CORE_DATA.persistentStoreCoordinator]; // Create a managed object context
        [newContext setStalenessInterval:0.0];
        [newContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];

        bgBlock(newContext);

    });
}

- (void)saveContext {
    [CGSharedCoreData saveContext:managedObjectContext_];
}

- (void)clearStore {

    NSError *error = nil;

    if  (self.persistentStoreCoordinator) {
        if ([persistentStoreCoordinator_ persistentStores] == nil) {
            CGLog(@"No persistent stores to clear!");
        }
        else {
            CGLog(@"Cleaning persistent stores!");

            managedObjectContext_ = nil;


            NSPersistentStore *store = [[persistentStoreCoordinator_ persistentStores] lastObject];

            if (![persistentStoreCoordinator_ removePersistentStore:store error:&error]) {
                CGLog(@"Unresolved error %@, %@", error, [error userInfo]);
                abort();
            }

            // Delete file
            if ([[NSFileManager defaultManager] fileExistsAtPath:store.URL.path]) {
                if (![[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error]) {
                    CGLog(@"Unresolved error %@, %@", error, [error userInfo]);
                    abort();
                }
            }

            // Delete the reference to non-existing store
            persistentStoreCoordinator_ = nil;
        }
    }
}

#pragma mark - Core Data stack

// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
- (NSManagedObjectContext *)managedObjectContext
{
     NSAssert([NSThread isMainThread], @"Must be instantiated on main thread.");

    if (managedObjectContext_ != nil) {
        return managedObjectContext_;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil)
    {
        managedObjectContext_ = [[NSManagedObjectContext alloc] init];
        [managedObjectContext_ setPersistentStoreCoordinator:coordinator];
        [managedObjectContext_ setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
    }

    return managedObjectContext_;
}

// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.

- (NSManagedObjectModel *)managedObjectModel
{

    if (managedObjectModel_ != nil) {
        return managedObjectModel_;
    }

    managedObjectModel_ = [NSManagedObjectModel mergedModelFromBundles:nil];
    return managedObjectModel_;
}

// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

    if (persistentStoreCoordinator_ != nil) {
        return persistentStoreCoordinator_;
    }

    CGLog(@"Creating a persistent store!");

    NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationLibraryDirectory] stringByAppendingPathComponent: @"CGChatData.sqlite"]];

    NSError *error = nil;
    persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {



        CGLog(@"Unresolved error %@, %@", error, [error userInfo]);
        //        abort();
        CGLog(@"Delete STORE: %d",[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]);
        persistentStoreCoordinator_ = nil;
        persistentStoreCoordinator_ = self.persistentStoreCoordinator;
        }

    return persistentStoreCoordinator_;
}

#pragma mark -
#pragma mark Handling Multiple Contexts

/**
 Merges the changes from the insert contexts to the main context on the main thread.
 */

- (void)mergeChanges:(NSNotification *)notification
{
    // Merge changes into the main context on the main thread
    if(![[CGLoginEngine sharedLoginEngine] isLoggedIn])
        return;

    if (![NSThread isMainThread])
    {
        [self performSelectorOnMainThread:@selector(mergeChanges:)
                               withObject:notification waitUntilDone:YES];
        return;
    }

    //CAUTION: Without the for clause below the NSFetchedResultsController may not capture all the changed objects.
    //For more info see: http://stackoverflow.com/questions/3923826/nsfetchedresultscontroller-with-predicate-ignores-changes-merged-from-different
    //and http://stackoverflow.com/questions/2590190/appending-data-to-nsfetchedresultscontroller-during-find-or-create-loop


    for (NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey])
    {
        [[managedObjectContext_ objectWithID:[object objectID]] willAccessValueForKey:nil];
    }


    [managedObjectContext_ mergeChangesFromContextDidSaveNotification:notification];

}

now , whenever i need to send a message , I am writing it to core data and updating the tableview with a fetched results controller. The writing part looks like this :

[CGSharedCoreData performInTheBackground:^(NSManagedObjectContext *blockContext)
         {
             CGLog(@"thread ******* is main %d",[NSThread isMainThread]);
            [[CGChatsModelController sharedChatModel] addChatWithtext:[chatMessage objectForKey:@"Message"]
                                                                              username:[chatMessage objectForKey:@"Receiver"]
                                                                             firstName:[chatMessage objectForKey:@"ReceiverFirstName"]
                                                                              lastName:[chatMessage objectForKey:@"ReceiverLastName"]
                                                                               imageId:[chatMessage objectForKey:@"ReceiverImageID"]
                                                                           createdByMe:YES
                                                                                  time:time
                                                                               context:blockContext];
              [CGSharedCoreData saveContext:blockContext];

But , When I send multiple messages in a very short span , it entirely blocks my UI, even when the core-data saving and all other related operations are being done on a background queue. Is there any particular reason for why ,this is happening ?

I am attaching a few other blocks of my code for reference here :

- (CGChat *) addChatWithtext:(NSString *)text username:(NSString *)username firstName:(NSString *)firstName lastName:(NSString *)lastName imageId:(NSString *)imageId createdByMe:(BOOL)yesOrNo time:(NSDate *)date context:(NSManagedObjectContext *)context
{
    NSManagedObjectContext *backgroundContext = context;
    CGChat *chat = (CGChat *)[NSEntityDescription insertNewObjectForEntityForName:@"CGChat" inManagedObjectContext:backgroundContext];
    chat.text = text;
    chat.createdByMe = [NSNumber numberWithBool:yesOrNo];


     chat.status = @"sent";


    [self addChat:chat toUserWithUserName:username firstName:firstName lastName:lastName imageID:imageId time:date WithContext:backgroundContext];

    return chat;

}

- (CGChat *)lookUpChatWithUserName:(NSString *)username text:(NSString *)text timeStamp:(NSString *)timeStamp createdByMe:(BOOL) yesOrNo context:(NSManagedObjectContext *)context

{
    NSFetchRequest *request = [[NSFetchRequest alloc] init];

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"CGChat" inManagedObjectContext:context];
    [request setEntity:entity];

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"userIchatWith == %@ && text == %@ && timeStamp == %@ && createdByMe ==%@", [self lookUpForUserWithUsername:username inContext:context],text,timeStamp,[NSNumber numberWithBool:yesOrNo]];

    [request setPredicate:predicate];

    NSArray *resultArray = nil;
    NSError *error;

    resultArray = [context executeFetchRequest:request error:&error];

    CGChat *chat = nil;
    if ([resultArray count] > 0) {
        chat  = [resultArray objectAtIndex:0];
    }
    return chat;

}


- (void) addChat:(CGChat *)chat toUserWithUserName:(NSString *)username firstName:(NSString *)firstName lastName:(NSString *)lastName imageID:(NSString *)imageId time:(NSDate *)date WithContext:(NSManagedObjectContext *)context

{

    CGUser *user = [self lookUpForUserWithUsername:username inContext:context];

    if (!user)
    {
        user = (CGUser *)[NSEntityDescription insertNewObjectForEntityForName:@"CGUser" inManagedObjectContext:context];
        user.userName = username ;


    }
    user.firstName = firstName;
    user.lastName = lastName;

    if (![user.imageID isEqualToString:imageId])
    {
        user.imageID = imageId;
    }


    CGChat *chats = [self getLastChatForUsername:username andContext:context];
    if(chats)
    {
        chats.isLastChat = [NSNumber numberWithBool:NO];
    }


    NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
    [dateFormat setDateFormat:@"yyyy-MM-dd HH:mm:ss"];

    chat.timeStamp = [dateFormat stringFromDate:date];
    chat.isLastChat = [NSNumber numberWithBool:YES];
    chat.userIchatWith = user;
    user.timeOfLatestChat = [dateFormat stringFromDate:date];

}


- (CGChat *) getLastChatForUsername:(NSString *)username andContext:(NSManagedObjectContext *)context
{

    CGUser *user = [self lookUpForUserWithUsername:username inContext:context];

    if(user)
    {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"isLastChat == %@",[NSNumber numberWithBool:YES]];

        NSSet *filteredSet = [user.chats filteredSetUsingPredicate:predicate];

        return [[filteredSet allObjects] lastObject];
    }
    else
    {
        return nil;
    }
}


- (CGUser *) lookUpForUserWithUsername:(NSString *)username inContext:(NSManagedObjectContext *)context
{
    NSFetchRequest *request = [[NSFetchRequest alloc] init];

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"CGUser" inManagedObjectContext:context];
    [request setEntity:entity];

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"userName == %@", username];
    [request setPredicate:predicate];

    NSArray *resultArray = nil;
    NSError *error;

    resultArray = [context executeFetchRequest:request error:&error];

    CGUser *user = nil;
    if ([resultArray count] > 0) {
        user  = [resultArray objectAtIndex:0];
    }

    return user;
}
shashank
  • 123
  • 14

1 Answers1

2

There are several issues here:

  1. Core Data does not throw NSException, do not wrap your Core Data calls in try/catch. That will just create noise if something else does throw an exception. try/catch is VERY rarely used in Objective-C programming.

  2. You are listening for and merging changes from NSManagedObjectContextDidSaveNotification calls when you are using a parent/child context design. This is incorrect. The parent/child relationship handles this for you, automatically, whenever you save the child context. When you listen for and consume that notification you are forcing Core Data to process that information a second time, on the main thread.

  3. It is not clear what "background context" you are passing around as you do not show the code that calls your -add... methods. Background contexts should not persist for long periods of time. They are really meant to be used and destroyed. The longer you have a background context in existence the further from the main context it is going to be as changes in the main context do NOT get passed down to the child contexts.

Update

My apologies, I misread the code. Since you are merging the changes from a background thread into the main thread, there is no way to avoid blocking the main thread. This is one of the primary reasons for the creation of the parent/child design.

The only way to avoid this, and it is not recommended would be to:

  1. Stand up a new NSPersistentStoreCoordinator pointed at the same sqlite file
  2. Update that NSPersistentStoreCoordinator from the background thread
  3. Notify the main NSManagedObjectContext to reload all data

This will avoid most of the main thread blocking but the expense in code complexity is almost always too much.

Marcus S. Zarra
  • 46,571
  • 9
  • 101
  • 182
  • @marcus : ** You are listening for and merging changes from NSManagedObjectContextDidSaveNotification calls when you are using a parent/child context design. This is incorrect. The parent/child relationship handles this for you, automatically, whenever you save the child context.** I am not using the parent -child context type ( nested contexts ). But rather using the pre- ios 5 Type of coredata merging . The 'background Context'is a new context being allocated every time i perform any process in the background queue. which i mentioned in the `code` + performInBackground method `code` – shashank Jun 04 '13 at 04:16
  • @ Marcus S. Zarra: here is the code for your reference . + (void)performInTheBackground:(void (^)(NSManagedObjectContext *blockContext))bgBlock { dispatch_async([CGSharedCoreData backgroundSaveQueue], ^{ NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] init]; [newContext setPersistentStoreCoordinator:CG_CORE_DATA.persistentStoreCoordinator]; // Create a managed object context [newContext setStalenessInterval:0.0]; [newContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy]; bgBlock(newContext); }); } – shashank Jun 04 '13 at 04:21
  • @Marcus S Zarra : Thank you . Will try this out . – shashank Jun 05 '13 at 04:23