2013-12-10 55 views
0

我正在製作一個應用程序,它解析鏈接返回的XML並使用核心數據保存該數據。問題是,爲了使UI保持響應,我必須在後臺線程中完成所有的解析。谷歌搜索了一段時間後,我在AppDelegate中設置了兩個上下文,一個用於處理背景解析,另一個用於加載用戶界面。關於使用核心數據和多線程的幾個問題

第一:這是爲我的情況設置兩個上下文的正確方法嗎?

//In AppDelegate.m 

//This one is for parsing in the background 
- (NSManagedObjectContext *)backgroundManagedObjectContext { 
    if (_backgroundManagedObjectContext != nil) { 
     return _backgroundManagedObjectContext; 
    } 

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
    if (coordinator != nil) { 
     _backgroundManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
     _backgroundManagedObjectContext.persistentStoreCoordinator = coordinator; 
    } 

    return _backgroundManagedObjectContext; 
} 

//This one is for updating the UI 
- (NSManagedObjectContext *)mainManagedObjectContext { 
    if (_mainManagedObjectContext != nil) { 
     return _mainManagedObjectContext; 
    } 

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
    if (coordinator != nil) { 
     _mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 
     _mainManagedObjectContext.persistentStoreCoordinator = coordinator; 
     _mainManagedObjectContext.parentContext = self.backgroundManagedObjectContext]; 
    } 

    return _mainManagedObjectContext; 
} 

第二:這是調用個人同步和加載方法的正確方法嗎?

然後我會打到我的同步方法(背景)像這樣:

[self.appDelegate.backgroundManagedObjectContext performBlock:^{ 
    [self syncData]; 
} 

,並呼籲給我的負荷的方法(UI)爲使

[self.appDelegate.mainManagedObjectContext performBlock:^{ 
    [self loadData]; 
} 

三:我的核心數據集中在一個實體(用戶)的所有其他實體連接到。我是否必須創建一個局部變量並在每次使用時都獲取它,或者是否有更好的方法,以便NSManagedObject不在兩個線程之間共享?

第四:如果我保存父背景上下文,子上下文(UI)會自動更新還是有我需要調用的特定方法?

+0

當'mainManagedObjectContext'被調用時'_backgroundManagedObjectContext'是否可能是'nil'? –

+0

@AaronBrager這就是我問這是否是正確設置的原因。我應該檢查' - (NSManagedObjectContext *)mainManagedObjectContext'方法嗎? – carloabelli

+0

或者你可以使用'[self backgroundManagedObjectContext]'而不是'_ _backgroundManagedObjectContext',如果它沒有實例化,它會實例化它。 –

回答

1

有了核心數據,看起來最好的學習方法就是與已有的工作進行比較。我會發布一些代碼來處理類似於你的情況。

第一:這是爲我的情況設置兩個上下文的正確方法嗎?

這是我在我的AppDelegate現在:

- (NSManagedObjectContext *)auxiliaryManagedObjectContext { 
    NSManagedObjectContext *managedObjectContext = nil; 

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
    if (coordinator != nil) { 
     managedObjectContext = [[NSManagedObjectContext alloc] init]; 
     [managedObjectContext setPersistentStoreCoordinator:coordinator]; 
     [managedObjectContext setUndoManager:nil]; 
    } 

    return managedObjectContext; 
} 

- (NSManagedObjectContext *)managedObjectContext 
{ 
    if (_managedObjectContext != nil) { 
     return _managedObjectContext; 
    } 

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
    if (coordinator != nil) { 
     _managedObjectContext = [[NSManagedObjectContext alloc] init]; 
     [_managedObjectContext setPersistentStoreCoordinator:coordinator]; 
    } 

    return _managedObjectContext; 
} 

二:這將是調用單獨的同步和 load方法的正確方法?

這真的取決於這些方法中發生了什麼。當您在後臺使用上下文時,應該發送設置通知,以便在發生更改時觸發。在這種方法中,你再合併從背景方面的變化到UI方面:

dispatch_queue_t background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long) NULL); 
dispatch_async(background, ^{ 


    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 
    NSManagedObjectContext *backgroundManagedObjectContext = [appDelegate auxiliaryManagedObjectContext]; 
    [[NSNotificationCenter defaultCenter] addObserver:self 
              selector:@selector(mergeContexts:) 
               name:NSManagedObjectContextDidSaveNotification 
               object:backgroundManagedObjectContext]; 

    NSManagedObject *threadSafeManagedObject = 
    [backgroundManagedObjectContext objectWithID:self.currentManagedObject.objectID]; 

    NSManagedObject *insertedThreadSafeManagedObject = [NSEntityDescription insertNewObjectForEntityForName:@"Entity" inManagedObjectContext:backgroundManagedObjectContext]; 

    NSError *error; 
    // Will call the mergeContexts: selector registered above 
    if(![backgroundManagedObjectContext save:&error]) { 
     NSLog(@"Error! %@", error); 
    } 
}); 

- (void)mergeContexts:(NSNotification *)notification { 
    SEL selector = @selector(mergeChangesFromContextDidSaveNotification:); 
    [self.managedObjectContext performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES]; 
} 

在mergeContexts:方法,背景上下文合併的UI環境。從背景開始的背景停留在背景中是非常重要的,反之亦然。此外,託管對象不是線程安全的,因此要在後臺線程中使用UI線程中的一個,必須將其轉移到後臺上下文中查看objectID,如上例所示。

第三:我的核心數據集中在一個實體(用戶)周圍,其他實體都連接到其他實體。我是否必須創建一個局部變量 並在每次使用時都獲取它,或者是否有更好的方法,以便在兩個線程之間不共享NSManagedObject?

上面的描述應該回答了這個問題。

第四:如果我救母的背景下,將孩子 上下文(UI)自動更新,或者是有我 需要調用特定的方法?

這將是上述示例中的mergeContexts:方法。讓我知道這是否合理。

編輯1:initWithConcurrencyType

正如你提到的,還有NSMainQueueConcurrencyType和NSPrivateQueueConcurrencyType是處理這一回之間的背景和UI背景下提出交換站管理對象上下文的初始化期間使用。

AppDelegate初始值設定項實際上是相同的。唯一的區別是初始化語句:

// Background context 
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 

// Main thread context 
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 

你可以這樣從你搶的背景情況下,在這種情況下的AppDelegate和使用後臺線程開展工作,完成主線程的工作:

AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 
NSManagedObjectContext *backgroundManagedObjectContext = [appDelegate auxiliaryManagedObjectContext]; 

[backgroundManagedObjectContext performBlock:^{ 
    // Do work 
    [self.managedObjectContext performBlock:^{ 
     // merge work into main context 
    }]; 
}]; 

重要的是聲明適用相同的線程安全規則(例如,必須將背景對象傳送到主線程視圖的objectID)。要將工作從後臺線程傳遞到主線程,應該在後臺上下文的執行塊中調用主線程上下文的performBlock。儘管合併上下文的便利性仍在於通知安排內容,無論您如何初始化上下文。

編輯2:父/子上下文

這裏是Correct implementation of parent/child NSManagedObjectContext父/子例如:

- (void)saveContexts { 
    [childContext performBlock:^{ 
     NSError *childError = nil; 
     if ([childContext save:&childError) { 
      [parentContext performBlock:^{ 
       NSError *parentError = nil; 
       if (![parentContext save:&parentError]) { 
        NSLog(@"Error saving parent"); 
       } 
      }]; 
     } else { 
      NSLog(@"Error saving child"); 
     } 
    }]; 
} 

在我們的例子中,初始化子上下文時,其父設置爲你的UI上下文:

[backgroundManagedObjectContext setParentContext:_managedObjectContext]; 
+0

謝謝您的詳細解答!它是有道理的,但我相信我在某處讀到'NSPrivateQueueConcurrencyType'和'NSMainQueueConcurrencyType'應該取代它。對於我的情況,使用示例代碼還是使用這些代碼會更好? – carloabelli

+0

我將添加一個如何使用它們的編輯。 – Chris

+0

另外,如果我有一個包含所有其他實體(通過關係)的實體,我可以在我的AppDelegate中創建它的兩個實例('self.appDelegate.user'&'self.appDelegate。backgroundUser'),這樣我所有的ViewController都可以訪問這個用戶(尤其是因爲我使用的是NSXMLParser),並且確保只在各自的線程中使用它們。 – carloabelli