簡體   English   中英

保存UIManagedDocument時應用程序崩潰

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM