2014-02-19 83 views
0

我從服務器檢索數據,我需要處理它。使用GCD和核心數據導致崩潰

對於每個密鑰,我創建一個NSManagedObject。每個對象都在相同的上下文中創建。我正在使用魔法記錄。

-(id)init { 
    if (self = [super init]){ 
     self.context = [NSManagedObjectContext MR_contextForCurrentThread]; 
    } 

    return self; 
} 

線程:

dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 
dispatch_group_t group = dispatch_group_create(); 

for (id key in boundariesDictionary) { 
    dispatch_group_async(group, queue, ^{ 
     DLog(@"nsthread: %@", [NSThread currentThread]); 

     NSString *boundaryIDString; 

     if ([key isKindOfClass:[NSString class]]) { 
      boundaryIDString = key; 
     } 
     else if ([key isKindOfClass:[NSNumber class]]) { 
      boundaryIDString = [key stringValue]; 
     } 

     if (boundaryIDString) { 
      DLog(@"boundaryIDString: %@", boundaryIDString) 

      NSDictionary *boundaryDictionary = [boundariesDictionary objectForKey:key]; 

      Boundary *boundary = [Boundary MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:@"boundaryID == %@ AND api == %@", [NSNumber numberWithInteger:[boundaryIDString integerValue]], self.serverCall.API] inContext:self.context]; 

      if ([boundaryDictionary objectForKey:AVI_NAME]) { 
       if (boundary == nil) { 
        DLog(@"creating boundary %@", boundaryIDString); 

        boundary = [Boundary MR_createInContext:self.context]; 
        boundary.boundaryID = [NSNumber numberWithInteger:[boundaryIDString integerValue]]; 
       } 
      } 

      boundary = [self processBoundary:boundary fromBoundaryDictionary:boundaryDictionary]; 
     } 
    } 
} 

[自processBoundary]只需要字典並將其設置爲管理對象的屬性。

if ([boundaryDictionary objectForKey:@"name"]) { 
    boundary.name = [boundaryDictionary objectForKey:@"name"]; 
} 

//more data processing 

這雖然導致錯誤:

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x1776f7f0> was mutated while being enumerated.' 

,如果我不使用相同的上下文爲每個線程運行正常。

我不明白什麼設置其他NSDictionary boundaryDictionary,我列舉通過。我根本不會突變boundariesDictionary,只會將數據複製到核心數據中。

當我PO對象(本例中爲0x1776f7f0)時,我得到一個集合中的Boundary對象列表。那些Boundary對象只會存在於NSManagedObjectContext「集合」中,我不會將它們添加到NSArray,NSDictionaryNSSet。但我不認爲我列舉了這一套。我通過創建新的邊界對象來改變它。

我覺得有些事情我不明白,或者完全沒有把握。

任何想法?

UPDATE:

for (id key in boundariesDictionary) { 
     NSString *boundaryIDString; 

     if ([key isKindOfClass:[NSString class]]) { 
      boundaryIDString = key; 
     } 
     else if ([key isKindOfClass:[NSNumber class]]) { 
      boundaryIDString = [key stringValue]; 
     } 

     if (boundaryIDString) { 
      [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) { 
       DLog(@"saveWithBlock thread: %@", [NSThread currentThread]); 

       NSDictionary *boundaryDictionary = [boundariesDictionary objectForKey:key]; 

       Boundary *boundary = [Boundary MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:@"boundaryID == %@ AND api == %@", [NSNumber numberWithInteger:[boundaryIDString integerValue]], self.serverCall.API] inContext:localContext]; 

       if ([boundaryDictionary objectForKey:AVI_NAME]) { 
        if (boundary == nil) { 
         boundary = [Boundary MR_createInContext:localContext]; 
         boundary.boundaryID = [NSNumber numberWithInteger:[boundaryIDString integerValue]]; 
        } 

        boundary = [self processBoundary:boundary fromBoundaryDictionary:boundaryDictionary]; 

        if (boundary == nil) { 
         //Prompt Error 
        } 
        else { 
         for (NSNumber *groupID in groupIDs) { 
          if ([groupID isKindOfClass:[NSNumber class]]) { 
           Group *group = [Group MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:@"groupID == %@ OR groupID == 0 AND api == %@", groupID, self.serverCall.API]]; 

           if (group != nil) { 
            group.lastUpdated = [NSDate date]; 

            [group addBoundariesObject:boundary]; 
           } 
           else { 
            DLog(@"group %@ DNE", groupID); 
           } 
          } 
         } 
        } 
       } 
      } completion:^(BOOL success, NSError *error) { 
       DLog(@"saveWithBlock completion Block | time: %f", [[NSDate date] timeIntervalSinceDate:startTime]); 
      }]; 
     } 
    } 

所以我Group 29應該看到所有我創建了邊界,但它不是。它不一致。有時會看到所有,有時會看到一些,有時看不到。

而且,我經常看到

NO CHANGES IN ** BACKGROUND SAVING (ROOT) ** CONTEXT - NOT SAVING 

在日誌中。它與我看到的這些消息的數量也不一致,而保存的上下文將插入多於一個的對象。

不知道它是否應該如何表現,似乎應該是1比1的比例,如果每個塊都有自己的上下文,並且每個塊只創建1個對象。

我記錄每個線程ID,並且它爲每個塊創建一個新的線程。沒有線程ID被記錄兩次,所以線程不應被重用。

回答

0

受管對象上下文不是線程安全的,不能使用它們或任何託管對象/集合/數據結構/無論它們可能從不同的隊列或線程返回。

我不知道神奇記錄是否支持它,但是您應該使用隊列遏制來處理您的上下文,然後您必須在MOC的私有隊列的上下文中執行所有操作。

如果您使用線程遏制,您必須保證上下文和由上下文創建的任何託管對象始終能夠連續訪問,而您在上面的代碼中完全沒有這樣做。

+0

魔法記錄有助於減少樣板編碼並有助於穿線。它可以讓你創建上下文並保存回所有新上下文產生的主上下文。它有助於使核心數據更容易線程安全。在這一點上,我還沒有保存它。只是試圖在同一個環境下創建它們。 – Padin215

+1

@ Log139我知道什麼是魔法記錄,它不會對你的代碼進行任何有用的幫助。它使用的代碼不是線程安全的。你無法在CoreData上合法地做你想做的事情。 CoreData需要您連續訪問上下文。 –

+0

不,核心數據當然可以與線程和隊列一起使用,如果您遵循一個簡單的規則,即您的託管對象和上下文不應該跨越線程邊界。這裏的問題是gcd隊列不是線程。他們重用線程,所以你可能違反了這個簡單的規則,將你的上下文綁定到線程並重用它。 – casademora

0

你現在面臨的問題是gcd排隊的線程不是一對一映射。 GCD重用線程,因此您可能在這裏不知不覺地跨越線程邊界。我的建議是簡單地創建一個新的上下文並停止使用contextForCurrentThread。我寫了關於on my blog問題的更多細節。 ContextForCurrentThread將在即將發佈的版本中被刪除。

+0

如果他正在使用串行隊列,他可能會考慮使用'dispatch_ [set/get] _specific'將上下文(或其他對象)直接與隊列而不是線程相關聯。他不是,所以這是一個評論,而不是一個答案。 – Tommy

+0

這完全錯了。他從多個線程同時訪問上下文和管理對象。線程遏制是有效的,因爲如果對上下文/對象的所有訪問都在同一個線程上,那麼保證是串行訪問。您還可以使用帶有線程控制的串行隊列,並獲得相同的效果,只要所有訪問都始終在串行隊列上執行的塊內完成。在這裏,他使用全局隊列,所以即使創建上下文也無濟於事,因爲它仍然會違反CoreData的多線程策略。 –

+0

@casademora我實現了[MagicalRecord saveWithBlock:^ {}]。它創建了新的實體,但它正在節約關係。每個邊界需要設置爲一個組。 group.boundaries不一致。假設我有20個邊界對象,都應該與一個組有關係。有時候,團隊有10個界限,有時候是16個,有時候是全部或者沒有關係。我會更新我的問題,包括我如何設置關係。 – Padin215