[英]App crashes when saving UIManagedDocument
我有一個應用程序,首先將一些數據加載到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;
}
你並沒有真正提供太多代碼。 你給出的唯一真實線索是你使用多個線程。
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主要是幫助您調試,而不是生產代碼所必需的。
我有預約,所以現在必須停下來。 但是,解決這些問題,現在就開始了
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.