2013-03-05 55 views
5

我有一個基於UIDocument的應用程序,它使用NSFileWrapper來存儲數據。 '主'文件包裝器包含許多附加的目錄文件包裝器,每個包裝器代表文檔的不同頁面。UIDocument&NSFileWrapper - NSFastEnumerationMutationHandler在保存期間更改文件包裝

每當我在UIDocument正在保存時對文檔進行更改(在writeContents:andAttributes:safelyToURL:forSaveOperation:error:中),應用程序崩潰。這裏是堆棧跟蹤:

UIDocument crash stack trace

似乎很清楚,我修改了UIDocument被列舉在背景文件中包裝的同一個實例。事實上,我檢查了在contentsForType:error:中返回數據模型的快照時,返回的子文件包裝指向與數據模型中當前駐留(和正在編輯)相同的對象,而不是副本。

- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError 
{ 
    if (!_fileWrapper) { 
     [self setupEmptyDocument]; 
    } 
    return [[NSFileWrapper alloc] initDirectoryWithFileWrappers:[_fileWrapper fileWrappers]]; 
} 

這是實施此方法的認可方法(根據WWDC 2012 Session 218 - Using iCloud with UIDocument)。

所以我想這個問題是:這種方法怎麼可以線程安全?

當主文件包裝的fileWrappers本身是目錄文件包裝時,情況有什麼不同嗎?如果制裁的方法是錯誤的,應該如何做

+0

我還沒有碰到這種情況,但它似乎是一個NSFileCoordinator可能做的工作嗎? – 2013-03-05 19:31:36

+0

@MikeM你可能是對的,因爲它可以防止崩潰,但我擔心它有可能真的放慢速度。應用程序中的更新通常很少且頻繁,因此應用程序需要最新的內容才能保持響應。我將不得不進一步調查這種方法,看看它是否可行。然而,問題仍然存在 - UIDocument使用不是線程安全的認可方法? – Stuart 2013-03-05 20:14:38

回答

6

如果您正在調用writeContents:...方法中的任何一種,則不應該這樣做。您應該打電話給saveToURL:forSaveOperation:completionHandler:writeContents:...方法適用於高級子類。

UIDocument使用兩個線程 - 主線程和「UIDocument File Access」線程(如果您子類更多UIDocument,則可以通過執行操作)。

線程安全與UIDocument就像Objective-C中的任何東西 - 只讓擁有一個對象的線程修改它。如果要更改的對象正在讀取,請在寫入完成後對其進行排隊以進行更改。也許更改你的UIDocument子類擁有的另一個對象,並將它們拉入contentsForType:error:中的新NSFileWrapper。傳遞一份fileWrappers NSDictionary的副本。

NSFileWrapper實際上將整個文檔加載到內存中。 NSFileWrapper實際上是在readFromURL:error:方法的「UIDocument File Access」線程中創建的,然後傳遞給loadFromContents:ofType:error:方法。如果你有一個大文件,這可能需要一段時間。

當你保存你通常想讓UIDocument決定什麼時候這樣做,並讓它知道通過updateChangeCount:方法(參數是UIDocumentChangeDone)已更改的東西。當你想節省現在你想要使用saveToURL:forSaveOperation:completionHandler:方法。

另一件需要注意的事項是UIDocument實現了NSFilePresenter協議,該協議定義了要使用的方法NSFileCoordinatorUIDocument只在座標文件上進行座標寫入,而不是子文件。你可能認爲協調文檔內的子文件可能會有所幫助,但是你得到的崩潰與迭代時改變字典有關,所以這不會有幫助。如果您(1)想要獲取文件更改的通知,或者(2)另一個對象或應用程序正在讀取/寫入同一文件,則只需要擔心編寫自己的NSFilePresenterUIDocument已經做了什麼可以正常工作。但是,在移動/刪除整個文檔時,您確實需要使用NSFileCoordinator

+0

感謝您的回覆。我已經瞭解了你所提到的大部分內容(但在這裏簡潔地說明它很好),例如我重寫'writeContents:...'並調用它的超級實現來實現預覽保存,並且我使用'updateChangeCount:'來​​標記所需的保存。我也知道'UIDocument'處理文件協調,但是在根文件包裝器上沒有協調寫入意味着對子文件的協調?從文件系統編程指南:「請注意:當一個'NSFileWrapper'實例被指定爲項目協調,所有的文件... – Stuart 2013-03-12 11:20:49

+0

...文件包裝中會自動將該文件的協調的一部分。」我目前正在使用一個單獨的數據對象來存儲數據(在''UIDocument'子類擁有的'Page'實例中),但是一旦發生更改,我會在根文件包裝器中放置一個新的文件包裝器是在Apple的CloudNotes示例應用程序中完成的,而不是等待並將其添加到'contentsForType:'中。正如你指出的那樣,這肯定是問題所在。我會推遲更新到根文件包裝,直到'UIDocument'請求一個快照,並看看是否一切正常。再次感謝。 – Stuart 2013-03-12 11:44:32

+0

文檔很混亂,我遇到了問題。所以我想我會盡可能多地覆蓋。您可能想要在其他地方進行預覽。 CloudNotes做了一些古怪的事情 - 它爲預覽節省了第二個UIDocument。如果您並不總是保留每個文檔的本地副本,那麼您確實只需要這樣做。是的,如果你協調根文件它可以應用於子文件,但據我所知,這是假設其他應用程序/對象協調根文件(iCloud&UIDocument這樣做)。 – Luke 2013-03-12 15:31:27