2012-06-11 14 views
5

這是我在Stack Overflow上的第一個問題,所以請原諒我打破任何禮節。我也是相當新的Objective-C /應用程序創建。UIManagedDocument在後臺線程中插入對象

我一直在關注CS193P斯坦福大學課程,特別是CoreData講座/演示。在Paul Hegarty的Photomania應用程序中,他從一個表視圖開始,並在後臺填充數據,而不會中斷UI流。我一直在創建一個列出當地業務的應用程序(來自返回JSON數據的api)。

我已經根據Paul的照片/攝影師課程創建了類別。類的創建本身不是問題,它是創建它們的地方。

​​

我的應用程序開始與幾個按鈕一個UIViewController,其中的每一個可以打開相應部分中的實現代碼如下(所有這些做工精細,我想提供足夠的信息讓我的問題是有道理的)。我調用一個幫助器方法來創建/打開基於this question的UIManagedDocument的URL。只要應用程序運行,就會調用它,並且它會很快加載。

我有非常類似保羅的fetchFlickrDataIntoDocument的方法:

-(void)refreshBusinessesInDocument:(UIManagedDocument *)document 
{ 
dispatch_queue_t refreshBusinessQ = dispatch_queue_create("Refresh Business Listing", NULL); 
dispatch_async(refreshBusinessQ, ^{ 
    // Get latest business listing 
    myFunctions *myFunctions = [[myFunctions alloc] init]; 
    NSArray *businesses = [myFunctions arrayOfBusinesses]; 

    // Run IN document's thread 
    [document.managedObjectContext performBlock:^{ 

     // Loop through new businesses and insert 
     for (NSDictionary *businessData in businesses) { 
      [Business businessWithJSONInfo:businessData inManageObjectContext:document.managedObjectContext]; 
     } 

     // Explicitly save the document. 
     [document saveToURL:document.fileURL 
      forSaveOperation:UIDocumentSaveForOverwriting 
      completionHandler:^(BOOL success){ 
       if (!success) { 
        NSLog(@"Document save failed"); 
       } 
      }]; 
     NSLog(@"Inserted Businesses"); 
    }]; 
}); 
dispatch_release(refreshBusinessQ); 
} 

[myFunctions arrayOfBusinesses]剛剛解析JSON數據,並返回一個包含個人businessses一個NSArray。

我已經在業務創建代碼的開始和結束處使用NSLog運行代碼。每個企業都分配了一個部分,需要0.006秒才能創建,並且有幾百個這樣的部分。插入結束大約需要2秒鐘。

輔助方法是在這裏:

// The following typedef has been defined in the .h file 
// typedef void (^completion_block_t)(UIManagedDocument *document); 

@implementation ManagedDocumentHelper 

+(void)openDocument:(NSString *)documentName UsingBlock:(completion_block_t)completionBlock 
{ 
    // Get URL for document -> "<Documents directory>/<documentName>" 
    NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 
    url = [url URLByAppendingPathComponent:documentName]; 

    // Attempt retrieval of existing document 
    UIManagedDocument *doc = [managedDocumentDictionary objectForKey:documentName]; 

    // If no UIManagedDocument, create 
    if (!doc) 
    { 
     // Create with document at URL 
     doc = [[UIManagedDocument alloc] initWithFileURL:url]; 

     // Save in managedDocumentDictionary 
     [managedDocumentDictionary setObject:doc forKey:documentName]; 
    } 

    // If the document exists on disk 
    if ([[NSFileManager defaultManager] fileExistsAtPath:[url path]]) 
    { 
     [doc openWithCompletionHandler:^(BOOL success) 
     { 
      // Run completion block 
      completionBlock(doc); 
     } ]; 
    } 
    else 
    { 
     // Save temporary document to documents directory 
     [doc saveToURL:url 
     forSaveOperation:UIDocumentSaveForCreating 
    completionHandler:^(BOOL success) 
     { 
      // Run compeltion block 
      completionBlock(doc); 
     }]; 
    } 
} 

,被稱爲在viewDidLoad中:

if (!self.lgtbDatabase) { 
    [ManagedDocumentHelper openDocument:@"DefaultLGTBDatabase" UsingBlock:^(UIManagedDocument *document){ 
     [self useDocument:document]; 
    }]; 
} 

useDocument只設置self.document所提供的文件。

我想更改此代碼,以便將數據插入到另一個線程中,並且用戶仍然可以單擊按鈕查看某個部分,而不會導致數據導入掛起UI。

任何幫助,將不勝感激我已經在這個問題上工作了幾天,並沒有能夠解決它,即使在這裏的其他類似的問題。如果您需要其他信息,請告訴我!

謝謝

編輯:

到目前爲止,這個問題已經收到一個向下票。如果有什麼方法可以改善這個問題,或者有人知道我找不到的問題,請您評論一下如何或在哪裏?如果您還有其他原因會導致投票失敗,請讓我知道,因爲我無法理解這種消極情緒,並且很想學習如何提供更好的貢獻。

回答

11

有幾種方法。

由於您使用UIManagedDocument你可以利用NSPrivateQueueConcurrencyType用於初始化一個新NSManagedObjectContext和使用performBlock做你的東西。例如:

// create a context with a private queue so access happens on a separate thread. 
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
// insert this context into the current context hierarchy 
context.parentContext = parentContext; 
// execute the block on the queue of the context 
context.performBlock:^{ 

     // do your stuff (e.g. a long import operation) 

     // save the context here 
     // with parent/child contexts saving a context push the changes out of the current context 
     NSError* error = nil; 
     [context save:&error]; 
}]; 

從上下文保存時,私有上下文的數據被推送到當前上下文。保存僅在內存中可見,因此您需要訪問主內容(鏈接到UIDocument)並在那裏進行保存(請參閱does-a-core-data-parent-managedobjectcontext-need-to-share-a-concurrency-type-wi)。

另一種方式(我最喜歡的)是創建一個NSOperation子類並在那裏做東西。例如,聲明NSOperation子像下面這樣:

//.h 
@interface MyOperation : NSOperation 

- (id)initWithDocument:(UIManagedDocument*)document; 

@end 

//.m 
@interface MyOperation() 

@property (nonatomic, weak) UIManagedDocument *document; 

@end 

- (id)initWithDocument:(UIManagedDocument*)doc; 
{ 
    if (!(self = [super init])) return nil; 

    [self setDocument:doc]; 

    return self; 
} 

- (void)main 
{ 
    NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init]; 
    [moc setParentContext:[[self document] managedObjectContext]]; 

    // do the long stuff here... 

    NSError *error = nil; 
    [moc save:&error]; 

    NSManagedObjectContext *mainMOC = [[self document] managedObjectContext]; 
    [mainMOC performBlock:^{ 
    NSError *error = nil; 
    [mainMOC save:&error]; 
    }]; 

    // maybe you want to notify the main thread you have finished to import data, if you post a notification remember to deal with it in the main thread... 
} 

現在在主線程可以提供操作像下面這樣的隊列:

MyOperation *op = [[MyOperation alloc] initWithDocument:[self document]]; 
[[self someQueue] addOperation:op]; 

附:您無法在NSOperationmain方法中啓動異步操作。 main完成後,與該操作鏈接的代理將不會被調用。要說出你可以解釋的事實,但這涉及到處理運行循環或併發行爲。

希望有所幫助。

+2

你是一個絕對的傳奇!我已經使用你的第一個解決方案,對我來說,它似乎更容易理解。不幸的是,我不能給你這樣的享受,因爲我只是剛剛加入,但當我可以的時候我會。 我會在晚些時候看看NSOperations,因爲這讓我有點困惑。再次感謝您的幫助! –

+0

不客氣。乾杯。 –

+2

只是爲了完成:不要忘記告訴你的UIManagedDocument它在你的(和其他)操作後處於一個骯髒的狀態。所以通過調用'[document updateChangeCount:UIDocumentChangeDone]'來強制保存。這是CS193P斯坦福大學課程中缺少的。 –

0

最初我只是要發表評論,但我想我沒有它的特權。我只是想指出UIDocument,超出更改計數提供 - (void)autosaveWithCompletionHandler:(void (^)(BOOL success))completionHandler

這應該不會有更新更改計數,因爲它等待一個「方便時刻」的經驗延遲。

+0

這不是一個答案 – johannes