我有一个应用程序首先将一些数据加载到UIManagedDocument中,然后执行saveToURL:forSaveOperation:completionHandler:
。在completionHandler块中,它会对此数据库的各种元素进行更新,一旦完成,它会进行另一次保存。
除此之外,该应用程序有3个按钮,分别重新加载数据,重新更新数据和删除数据库的一个实体。在每个按钮方法中,最后一条指令也是保存。
当我在模拟器中运行所有这些时,一切顺利。但在设备中没有。它经常崩溃。我观察到,通常,当按下“删除”按钮或重新加载或重新更新数据库时,它会崩溃。而且它始终在saveToURL
操作中
在我看来,当有多个线程保存数据库时会出现问题。由于设备执行代码较慢,可能同时存在多个节省,并且应用程序无法正确处理它们。此外,有时删除按钮不会删除实体,并表示不存在(当它执行时)。
我对此完全感到困惑,所有这些保存操作都必须完成......事实上,如果我删除它们,那么应用程序的行为就会更加不连贯。
有什么建议可以解决这个问题吗?非常感谢你!
[编辑]我在这里发布有问题的代码。首先加载数据,我使用一个辅助类,特别是这两个方法:
+ (void)loadDataIntoDatabase:(UIManagedDocument *)database
{
[database.managedObjectContext performBlock:^{
// Read from de plist file and fill the database
[database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
[DataHelper completeDataOfDatabase:database];
}];
}
+ (void)completeDataOfDatabase:(UIManagedDocument *)database
{
[database.managedObjectContext performBlock:^{
// Read from another plist file and update some parameters of the already existent data (uses NSFetchRequest and works well)
// [database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:nil];
[database updateChangeCount:UIDocumentChangeDone];
}];
}
在视图中,我有3个动作方法,如:
- (IBAction)deleteButton {
[self.database.managedObjectContext performBlock:^{
NSManagedObject *results = ;// The item to delete
[self.database.managedObjectContext deleteObject:results];
// [self.database saveToURL:self.database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
[self.database updateChangeCount:UIDocumentChangeDone];
}];
}
- (IBAction)reloadExtraDataButton {
[DataHelper loadDataIntoDatabase:self.database];
// [self.database saveToURL:self.database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
[self.database updateChangeCount:UIDocumentChangeDone];
}
- (IBAction)refreshDataButton {
[DataHelper completeDataOfDatabase:self.database];
//[self.database saveToURL:self.database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
[self.database updateChangeCount:UIDocumentChangeDone];
}
[编辑2]更多代码:首先,初始视图以这种方式执行viewDidLoad:
- (void)viewDidLoad{
[super viewDidLoad];
self.database = [DataHelper openDatabaseAndUseBlock:^{
[self setupFetchedResultsController];
}];
}
这就是setupFetchedResultsController方法的样子:
- (void)setupFetchedResultsController
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Some entity name"];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.database.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
}
应用程序的每个视图(它有标签)都有一个不同的setupFetchedResultsController,以显示数据库包含的不同实体。
现在,在helper类中,这是通过每个视图的viewDidLoad执行的第一个类方法:
+ (UIManagedDocument *)openDatabaseAndUseBlock:(completion_block_t)completionBlock
{
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:@"Database"];
UIManagedDocument *database = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[database.fileURL path]]) {
[database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
[self loadDataIntoDatabase:database];
completionBlock();
}];
} else if (database.documentState == UIDocumentStateClosed) {
// Existe, pero cerrado -> Abrir
[database openWithCompletionHandler:^(BOOL success) {
[self loadDataIntoDatabase:database];
completionBlock();
}];
} else if (database.documentState == UIDocumentStateNormal) {
[self loadDataIntoDatabase:database];
completionBlock();
}
return database;
}
答案 0 :(得分:4)
你并没有真正提供太多代码。你给出的唯一真实线索是你使用多个线程。
UIManagedDocument有两个ManagedObjectContexts(一个为主队列指定,另一个为私有队列指定),但它们仍然必须只能从它们自己的线程中访问。
因此,您只能在主线程中使用managedDocument.managedObjectContext。如果要从另一个线程使用它,则必须使用performBlock或performBlockAndWait。同样,你永远不会知道你在私有线程上运行父上下文,所以如果你想专门做一些事情,你必须使用performBlock *。
最后,你真的不应该调用saveToURL,除非你最初创建数据库。 UIManagedDocument将自动保存(在其自己的时间内)。
如果你想鼓励它提前保存,你可以发送它updateChangeCount:UIDocumentChangeDone告诉它它有需要保存的更改。
修改强>
您应该只在第一次创建文件时调用saveToURL。使用UIManagedDocument,无需再次调用它(实际上它可能会导致一些意外的问题)。
基本上,在创建文档时,请勿在完成处理程序执行之前设置iVar。否则,您可能正在使用处于部分状态的文档。在这种情况下,请在完成处理程序中使用这样的帮助程序。
- (void)_document:(UIManagedDocument*)doc canBeUsed:(BOOL)canBeUsed
{
dispatch_async(dispatch_get_main_queue(), ^{
if (canBeUsed) {
_document = doc;
// Now, the document is ready.
// Fire off a notification, or notify a delegate, and do whatever you
// want... you really should not use the document until it's ready, but
// as long as you leave it nil until it is ready any access will
// just correctly do nothing.
} else {
_document = nil;
// Do whatever you want if the document can not be used.
// Unfortunately, there is no way to get the actual error unless
// you subclass UIManagedDocument and override handleError
}
}];
}
初始化您的文档,例如......
- (id)initializeDocumentWithFileURL:(NSURL *)url
{
if (!url) {
url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:@"Default_Project_Database"];
}
UIManagedDocument *doc = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[doc.fileURL path]]) {
// The file does not exist, so we need to create it at the proper URL
[doc saveToURL:doc.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
[self _document:doc canBeUsed:success];
}];
} else if (doc.documentState == UIDocumentStateClosed) {
[doc openWithCompletionHandler:^(BOOL success) {
[self _document:doc canBeUsed:success];
}];
} else {
// You only need this if you allow a UIManagedDocument to be passed
// in to this object -- in which case the code above that initializes
// the <doc> variable will be conditional on what was passed...
BOOL success = doc.documentState == UIDocumentStateNormal;
[self _document:doc canBeUsed:success];
}
}
上面的“模式”是必要的,以确保在完全可以使用之前不要使用该文档。现在,这段代码应该是您调用saveToURL的唯一时间。
请注意,根据定义,document.managedObjectContext的类型为NSMainQueueConcurrencyType。因此,如果您知道您的代码在主线程上运行(就像所有UI回调一样),则不必使用performBlock。
但是,如果您实际在后台进行加载,请考虑..
- (void)backgroundLoadDataIntoDocument:(UIManagedDocument*)document
{
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = document.managedObjectContext;
[moc performBlock:^{
// Do your loading in here, and shove everything into the local MOC.
// If you are loading a lot of stuff from the 'net (or elsewhere),
// consider doing it in strides, so you deliver objects to the document
// a little at a time instead of all at the end.
// When ready to save, call save on this MOC. It will shove the data up
// into the MOC of the document.
NSrror *error = nil;
if ([moc save:&error]) {
// Probably don't have to synchronize calling updateChangeCount, but I do it anyway...
[document.managedObjectContext performBlockAndWait:^{
[document updateChangeCount:UIDocumentChangeDone];
}];
} else {
// Handle error
}
}];
}
您可以将其父项提交给parentContext,而不是将您的后台MOC提供给mainMOC。加载然后保存到它将把更改置于主MOC“上方”。主MOC将在下次执行获取操作时看到这些更改(请注意NSFetchRequest的属性)。
注意:有些人报告过(在Erica Sadun的书中也有说明),在第一次saveToURL之后,你需要关闭,然后打开以使一切正常。
修改强>
这真的很长。如果你有更多积分,我建议聊聊。实际上,我们不能通过SO来实现,但我们可以通过其他媒介来实现。我会尽量简短,但请回去重读我发布的内容,并小心注意,因为您的代码仍然违反了几个租户。
首先,在viewDidLoad()中,您直接将文档分配给调用openDatabaseAndUseBlock的结果。当时文档未处于可用状态。在完成处理程序触发之前,您不希望文档可访问,这在openDatabaseAndUseBlock()返回之前不会发生。
其次,只在您第一次创建数据库时调用saveToURL(在openDatabaseAndUseBlock()内)。不要在其他任何地方使用它。
第三。注册通知中心以接收所有事件(只记录它们)。这将极大地帮助您进行调试,因为您可以看到正在发生的事情。
第四,子类UIManagedDocument,并覆盖handleError,看看它是否被调用...这是唯一的方法,如果/当它发生时你会看到确切的NSError。
3/4主要是帮助您调试,而不是生产代码所必需的。
我有预约,所以现在必须停下来。但是,请解决这些问题,并在此处