2010-01-26 61 views
5

我正面臨着非常煩人的問題。我的iPhone應用程序正在從網絡服務器加載它的數據。數據以plist的形式發送,解析時,它需要使用CoreData存儲到SQLite數據庫。使用CoreData在iPhone上導入大型數據集

問題是,在某些情況下,這些數據集太大(5000+條記錄)並且導入時間過長。更多的是,當iPhone試圖暫停屏幕時,看門狗殺死了應用程序,因爲它仍在處理導入,並且最多不會響應5秒,因此導入永遠不會結束。

根據文章「Efficiently Importing Data」http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/CoreData/Articles/cdImporting.html以及其他關於此的文檔,我使用了所有推薦的技術,但它仍然非常慢。

解決方案我正在尋找的是讓應用程序暫停,而是讓進口跑在後面的(更好的),或防止企圖中止應用在所有。或者更好的主意也歡迎。

有關如何克服這些問題的任何提示都非常感謝! 感謝

+0

這個問題是怎麼發生的?我正在處理類似大小的數據集(如果不大),並且需要每天從Web服務中將其拉下一次。我正在考慮下載夜間準備的.sqlite文件與實際的Web服務。 – Augie 2012-07-10 20:51:47

+2

我認爲加載準備好的sqlite文件是這種情況下最好的解決方案,或者至少是最簡單的一種。無論如何,其他解決方案太複雜,手機設備性能差。 – Matthes 2012-07-11 09:48:47

回答

0

有沒有什麼建議可以提前將數據打包任何方式 - 在開發過程中說的?當你將應用推送到商店時,一些數據已經在那裏了?這將減少你必須提取的數據量,從而幫助解決這個問題?

如果數據是時間敏感的,或者沒有準備好,或者出於某種原因,你不能做到這一點,您可以使用zlib壓縮你船它在網絡上之前壓縮數據?

或者是手機死於5K +插入的問題?

+0

感謝您的快速回復。是的,問題在於它在5K +插件上死亡。數據由服務器壓縮,所以下載時間不是問題。不幸的是,由於它是基於時間的更新,因此無法預加載或緩存。 – Matthes 2010-01-26 16:40:48

+0

那麼,如何將插入物分成10x500個物品插入物?這實際上只做對了一次?在應用程序的首次發佈? 另外,也許你只是在不同的線程中根據需要拉下部分數據。如果數據在服務器上被分割,那麼您將能夠更好地識別出您需要的細分,並且只提取這些細分? – 2010-01-27 01:13:21

4

而不是將plist文件推送到手機,您可能想發送準備使用sqlite文件。這樣做有很多好處:

  1. 不需要輸入手機
  2. 更緊湊

。如果你總是更換整個內容簡單地覆蓋永久存儲在設備中。否則,你可能想要維護一個數組作爲你所下載的所有sqlite的plist,然後使用它將所有商店添加到persistentStoreCoordinator。

底線:使用多個預編譯sqlite的文件,並將它們添加到persistentStoreCoordinator。

您可以使用iPhone模擬器創建這些CoreData,SQLite的,存儲或使用一個獨立的Mac應用程序。你需要自己寫這兩個。

0

我想你並沒有向客戶展示所有5K記錄?我建議在服務器上完成所有您需要的聚合,然後僅將必要的數據發送到手機。即使這涉及到生成幾個不同的數據視圖,它仍然會比發送(然後處理)iPhone中的所有行快幾個數量級。

你還在單獨的(非事件/ UI)線程中處理數據嗎?

+0

這種數據量一次加載的原因是預緩存。即使您處於離線狀態,數據仍然可用,因此該應用程序仍可用。不幸的是,這是這個應用程序的核心原則,所以沒有辦法改變它... – Matthes 2010-01-27 09:58:57

2

我通過將插入處理放在後臺線程中解決了類似的問題。但是,首先我創建了一個進度警報,以便用戶在插入條目時無法操作數據存儲。

這基本上是ViewControllers viewDidLoad中

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    NSError *error = nil; 
    if (![[self fetchedResultsController] performFetch:&error]) { 
     NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
     abort(); 
    } 

    // Only insert those not imported, here I know it should be 2006 entries 
    if ([self tableView:nil numberOfRowsInSection:0] != 2006) { 

     // Put up an alert with a progress bar, need to implement 
     [self createProgressionAlertWithMessage:@"Initilizing database"]; 

     // Spawn the insert thread making the app still "live" so it 
     // won't be killed by the OS 
     [NSThread detachNewThreadSelector:@selector(loadInitialDatabase:) 
           toTarget:self 
         withObject:[NSNumber numberWithInt:[self tableView:nil 
               numberOfRowsInSection:0]]]; 
    } 
} 

插入線程這樣

- (void)loadInitialDatabase:(NSNumber*)number 
{ 
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 

    int done = [number intValue]+1; // How many done so far 

    // I load from a textfile (csv) but imagine you should be able to 
    // understand the process and make it work for your data 
    NSString *file = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] 
               pathForResource:@"filename" 
                 ofType:@"txt"] 
               encoding:NSUTF8StringEncoding 
                error:nil]; 

    NSArray *lines = [file componentsSeparatedByString:@"\n"]; 

    float num = [lines count]; 
    float i = 0; 
    int perc = 0; 

    for (NSString *line in lines) { 
     i += 1.0; 

     if ((int)(i/(num*0.01)) != perc) { 
      // This part updates the alert with a progress bar 
      // setProgressValue: needs to be implemented 
      [self performSelectorOnMainThread:@selector(setProgressValue:) 
            withObject:[NSNumber numberWithFloat:i/num] 
           waitUntilDone:YES]; 
      perc = (int)(i/(num*0.01)); 
     } 

     if (done < i) // keep track of how much done previously 
      [self insertFromLine:line]; // Add to data storage... 

    } 

    progressView = nil; 
    [progressAlert dismissWithClickedButtonIndex:0 animated:YES]; 
    [pool release]; 
} 

這是一個有點粗糙這種方式,它會嘗試初始化從它留下的,其中數據存儲完成如果用戶發生以前的時間停止它...

+0

感謝您的所有答覆。然而,對於我來說,沒有任何提議的解決方案似乎是正確的。 首先,這是定期的數據更新,而不是在首次啓動應用程序時進行一次數據庫初始化。它會在一段時間內檢查數據。 目前,數據以plist xml格式下載並解析。這一點沒問題。 然後,數據一次導入500個左右的記錄(嘗試各種批量大小的值,但沒有顯着影響)。 這一切都是在後臺完成,同時顯示了旋轉輪的啓動屏幕。 但是,問題仍然保持不變:( – Matthes 2010-01-27 09:34:21

+0

所以我試圖把數據庫導入部分分開線程(我不知道,實際上它在主線程上執行),它的方式,該應用程序沒有被殺死OS,當你指出切換到掛起模式時,所以這部分已經解決 - 謝謝你的提示,但是導入如此大量的數據所需的時間仍然不可接受 - 大約需要5分鐘的時間完成! – Matthes 2010-01-27 12:49:03

4

首先,如果你可以打包數據與應用程序,這將是理想的。

但是,假設你不能這樣做,那麼我會做那麼以下:

  1. 一旦數據之前進口其下載突破到多個文件
  2. 導入後臺線程,一次一個文件。
  3. 一旦文件被導入並保存,刪除導入文件。
  4. 啓動時,查找等待處理的文件並從中斷處繼續。

理想情況下,與應用程序一起發送數據的工作量會少得多,但第二種解決方案可以正常工作,您可以在開發過程中對數據分解進行微調。

+0

謝謝這似乎是一個很好的解決方案,但是請記住我正在處理XML文件,所以很可能在合理的位置拆分XML(保持有效)需要另一個處理時間,這可能也會很長(處理XML上的iPhone是非常痛苦和緩慢的,正如你可能知道的那樣)。所以,可能對我來說也沒有選擇:( – Matthes 2010-01-27 10:59:57

+0

由於數據是作爲plist來的,把它拉到NSDictionary中,spl它將字典分開並將其寫回。如果那是在解析所有這些數據並將其注入到Core Data中的時候,我會非常驚訝。 – 2010-01-28 09:44:54

0

您可以設置您的服務器端以暴露RESTful Web服務來處理您的數據嗎?我有類似的問題,並能夠通過RESTful Web服務公開我的信息。 iPhone上有一些庫可以很容易地從web服務中讀取數據。我選擇了從服務請求JSON並使用iPhone上的SBJSON庫來快速獲取我得到的結果並將它們轉換爲詞典以便於使用。我使用ASIHTTP庫進行Web請求並排隊跟進請求並使它們在後臺運行。

關於REST的好處是,它是一種內置的方式讓您獲取批量信息,因此您無需任意弄清楚如何分解要輸入的文件。您只需設置多少記錄就可以找回,而下一個請求則會跳過那麼多記錄。我不知道這對你來說是否是一種選擇,所以我現在不會介紹很多代碼示例,但如果可能的話,它可能是一種平滑的方式來處理它。

0

讓我們接受Restful(懶加載)不是一個選項......我明白你想要複製。如果負載問題類型的「少排在越來越多的時間加載),然後在僞代碼...

[self sQLdropIndex(OffendingIndexName)] 
[self breathInOverIP]; 
[self breathOutToSQLLite]; 
[self sQLAddIndex(OffendingIndexName)] 

這應該告訴你很多。

1

我有一個類似的問題,導入許多對象到CoreData中。起初,我在每個對象上創建&插入後,在託管對象上下文上做了一個save

你應該做的是創建/初始化你想要保存在CoreData中的每個對象,並且在所有遠程數據循環後+創建對象,然後執行託管對象上下文save

我想你可以把這看作是在SQLite數據庫中做一個事務:開始事務,做大量的插入/更新,結束事務。

如果這仍然是過於冗長,只是線程織補任務,並防止用戶交互,直至完全

0

我對經常必須處理100K插入,刪除和更新與核心數據的應用工作。如果它在5K刀片上窒息,那麼需要做一些優化。

首先,創建一些NSOperation子類來處理數據。覆蓋它的-main方法來執行處理。但是,此方法不能保證在主線程上運行。事實上,它的目的是爲了避免在主線程上執行代價昂貴的代碼,從而導致嚴重凍結,從而影響用戶體驗。因此,在-main方法中,您需要創建另一個託管對象上下文,它是您的主線程託管對象上下文的子項。

- (void)main 
{ 
    NSManagedObjectContext *ctx = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    [ctx setPersistentStoreCoordinator:mainManagedObjectContext.persistentStoreCoordinator]; 
    [ctx setUndoManager:nil]; 
    // Do your insertions here! 
    NSError *error = nil; 
    [ctx save:&error]; 
} 

根據你的情況,我不認爲你需要一個撤銷管理器。由於核心數據正在跟蹤您的更改,因此將會導致性能損失。

使用THIS上下文在-main方法中執行所有CRUD操作,然後保存該託管對象上下文。無論您的主線程的託管對象上下文擁有什麼,都必須註冊以響應名爲NSManagedObjectContextDidSaveNotification的NSNotification。註冊像這樣:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mocDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:nil]; 

然後定義選擇:

- (void)mocDidSaveNotification:(NSNotification *)notification 
{ 
    NSManagedObjectContext *ctx = [notification object]; 
    if (ctx == mainManagedObjectContext) return; 
    [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; 
} 

當這一切走到一起,它可以讓你執行在後臺線程長時間操作而不會阻塞UI線程。這種體系結構有幾種不同的形式,但其核心主題是:在BG線程上處理,在主線程上合併,更新UI。還有一些需要注意的事項:(1)在處理過程中保持一個自動釋放池,並且每隔一段時間耗盡一次以減少內存消耗。在我們的例子中,我們每1000個對象就做一次。根據您的需要進行調整,但請記住,根據每個對象所需的內存量,耗盡可能會很昂貴,因此您不想經常這樣做。 (2)儘量減少你的數據到絕對的最低限度,你需要有一個功能的應用程序。通過減少要解析的數據量,可以減少保存所需的時間。 (3)通過使用這種多線程方法,您可以同時處理您的數據。因此,創建3-4個NSOperation子類的實例,每個實例只處理一部分數據,以便它們全部同時運行,從而導致分析數據集所需的實時時間更少。