写入核心数据,即使在后台线程上完成,也会大大阻止UI

时间:2013-06-03 13:17:24

标签: ios multithreading core-data

Iam尝试基于聊天的应用。我已经设置了一个单身coredata经理如下

#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];

}

现在,每当我需要发送消息时,我都会将其写入核心数据并使用获取的结果控制器更新tableview。写作部分如下所示:

[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];

但是,当我在很短的时间内发送多条消息时,它会完全阻止我的UI,即使核心数据保存和所有其他相关操作都在后台队列上完成。 是否有任何特殊原因,这种情况正在发生?

我附上了我的代码的其他一些块供参考:

- (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;
}

1 个答案:

答案 0 :(得分:2)

这里有几个问题:

  1. Core Data不会抛出NSException,也不会将您的Core Data调用包装在try/catch中。如果 else 会引发异常,那只会产生噪音。 try/catch在Objective-C编程中很少使用非常

  2. 在使用父/子上下文设计时,您正在侦听并合并来自NSManagedObjectContextDidSaveNotification调用的更改。这是不正确的。无论何时保存子上下文,父/子关系都会自动为您处理。当您收听并使用该通知时,您将强制Core Data在主线程上再次处理该信息。

  3. 由于您没有显示调用-add...方法的代码,因此不清楚您传递的是什么“背景上下文”。背景情况不应该持续很长一段时间。它们真的意味着被使用和销毁。存在背景上下文的时间越长,主要上下文越远,主要上下文中的更改 NOT 就会传递给子上下文。

  4. 更新

    道歉,我误解了代码。由于您要将后台线程中的更改合并到主线程中,因此无法避免阻塞主线程。这是创建父/子设计的主要原因之一。

    避免这种情况的唯一方法是不建议:

    1. 站起来指向同一个sqlite文件的新NSPersistentStoreCoordinator
    2. 从后台主题
    3. 更新NSPersistentStoreCoordinator
    4. 通知主NSManagedObjectContext重新加载所有数据
    5. 这将避免主线程阻塞的大多数,但代码复杂性的代价几乎总是太多。