写入 coredata,即使在后台线程上完成,也会严重阻塞 UI
Posted
技术标签:
【中文标题】写入 coredata,即使在后台线程上完成,也会严重阻塞 UI【英文标题】:Writing to coredata, even when done on background thread , blocks the UI considerably 【发布时间】:2013-06-03 13:17:24 【问题描述】:我正在尝试一个基于聊天的应用程序。我已经设置了一个单例核心数据管理器,如下所示
#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://***.com/questions/3923826/nsfetchedresultscontroller-with-predicate-ignores-changes-merged-from-different
//and http://***.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】:这里有几个问题:
Core Data 不会抛出 NSException
,不要将您的 Core Data 调用包装在 try/catch
中。如果 else 确实引发了异常,那只会产生噪音。 try/catch
在 Objective-C 编程中非常很少使用。
当您使用父/子上下文设计时,您正在侦听和合并来自 NSManagedObjectContextDidSaveNotification
调用的更改。这是不正确的。每当您保存子上下文时,父/子关系都会自动为您处理此问题。当您侦听并使用该通知时,您将强制 Core Data 在主线程上第二次处理该信息。
由于您没有显示调用 -add...
方法的代码,因此不清楚您传递的是什么“背景上下文”。背景上下文不应持续很长时间。它们确实是要被使用和销毁的。背景上下文存在的时间越长,它就会离主上下文越远,因为主上下文中的更改不会传递给子上下文。
更新
抱歉,我误读了代码。由于您将后台线程的更改合并到主线程中,因此无法避免阻塞主线程。这是创建父/子设计的主要原因之一。
避免这种情况的唯一方法是:
-
新的
NSPersistentStoreCoordinator
指向同一个sqlite文件
从后台线程更新 NSPersistentStoreCoordinator
通知主NSManagedObjectContext
重新加载所有数据
这将避免大部分主线程阻塞,但代码复杂性的开销几乎总是太多。
【讨论】:
@marcus : ** 当您使用父/子上下文设计时,您正在侦听和合并来自 NSManagedObjectContextDidSaveNotification 调用的更改。这是不正确的。每当您保存子上下文时,父/子关系都会自动为您处理此问题。** 我没有使用父子上下文类型(嵌套上下文)。而是使用 pre-ios 5 类型的 coredata 合并。 “后台上下文”是每次我在后台队列中执行任何进程时分配的新上下文。我在code
+ performInBackground 方法中提到的code
@Marcus S. Zarra:这是供您参考的代码。 + (void)performInTheBackground:(void (^)(NSManagedObjectContext *blockContext))bgBlock dispatch_async([CGSharedCoreData backgroundSaveQueue], ^ NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] init]; [newContext setPersistentStoreCoordinator:CG_CORE_DATA.persistentStoreCoordinator]; / / 创建一个托管对象上下文 [newContext setStalenessInterval:0.0]; [newContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy]; bgBlock(newContext); );
@Marcus S Zarra:谢谢。会试试这个。以上是关于写入 coredata,即使在后台线程上完成,也会严重阻塞 UI的主要内容,如果未能解决你的问题,请参考以下文章
CoreData 在后台 MOC 的保存操作期间无法完成故障
为啥即使在指定返回 - 后台线程到主线程问题后代码也会执行?