一个线程中对 MOC 属性的更改未传播到主线程

Posted

技术标签:

【中文标题】一个线程中对 MOC 属性的更改未传播到主线程【英文标题】:Changes to MOC properties in one thread not propagated to main thread 【发布时间】:2013-01-08 23:28:30 【问题描述】:

我在执行获取请求并更新 4 个属性的线程中有第二个 MOC。 在主线程中,我注册了 NSManagedObjectDidSaveNotification 处理程序,该处理程序合并了我的第二个 MOC 的更改。 如果我在这个方法中设置一个断点并检查更新的对象,那么我确实会看到新的属性。但是,如果我在数组中添加对象 ID 并在其 ID 的帮助下发布合并+保存访问相同的对象,那么我会得到旧属性。

令人惊讶的是,我发现在 2 个属性中,一个属性具有新价值,而另一个没有。这令人难以置信,经过几个小时的调试,我无法理解这背后的原因。 如果天才能对这个问题有所了解,将会很有帮助。

更新

在我的主线程中:

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


- (NSManagedObjectContext *)mainThreadObjectContext 


    if (myMainThreadObjectContext == nil) 
       NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) 
        myMainThreadObjectContext = [[NSManagedObjectContext alloc] init];
        [myStateManagedObjectContext setPersistentStoreCoordinator:coordinator];
        [myStateManagedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
        [myStateManagedObjectContext setRetainsRegisteredObjects:NO];
        [myStateManagedObjectContext setUndoManager:nil];
    

return myMainThreadObjectContext;

我在这个方法中合并了线程 MOC 中所做的更改

- (void) contextDidSave:(NSNotification *)notification
  
if (![NSThread isMainThread])

    [self performSelectorOnMainThread:@selector(contextDidSave:) withObject:notification waitUntilDone:NO];
    return;

NSManagedObjectContext *moc = [notification object];
NSMutableSet *updatedObjects = [NSMutableSet set];

if (![moc isEqual:self.mainThreadManagedObjectContext] && [[moc persistentStoreCoordinator] isEqual:self.persistentStoreCoordinator])

    @synchronized(self)
    

            NSDictionary *userinfoDict = [notification userInfo];
            NSSet *insertedObjectSet = [userinfoDict valueForKeyPath:@"inserted.objectID"];
            NSSet *updatedObjectSet =  [userinfoDict valueForKeyPath:@"updated.objectID"];
            [updatedObjects addObjectsFromArray:insertedObjectSet.allObjects];
            [updatedObjects addObjectsFromArray:updatedObjectSet.allObjects];



        @try 
            [self.mainThreadManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
        
        @catch (NSException * e) 
            NSLog(@"Exception %@ when mergning contexts in -[%@ %@]", e, NSStringFromClass(self.class), NSStringFromSelector(_cmd));
        


        if(updatedObjects.count>0)
        
            [self performSelector:@selector(updatedStatus:) withObject:updatedObjects afterDelay:0.5];
        

        NSError *saveError = nil;
        if (![self.mainThreadManagedObjectContext save:&saveError])
        
            NSLog(@"Could not save objects in -[%@ %@]. Error = %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), saveError);
        

       


  
  

在主线程中,我有从 MOC 获取现有对象并更新从服务器接收到的属性的方法。 但是,在我的辅助线程中,我有一些方法可以插入新的托管对象,然后更新需要很长时间才能在主线程中处理的属性

在第二个线程中,我有以下代码

-(void) startTaskThread

    thread_ = [[NSThread alloc] initWithTarget:self selector:@selector(executeTask)     
    object:nil];
    [thread_ start];



-(void) executeTask

  @synchronized(self)
  
    [self saveContext];

     NSLog(@"\n\nStarting Task thread [%@]!!!!\n\n", self);

    for(;;)
    
        if([[NSThread currentThread] isCancelled])
            break;

        @autoreleasepool
        
            [condition_ lock];

            if(taskArray_.count==0)
            
                [condition_ wait];
            

            [condition_ unlock];

            @try 
                if(taskArray_.count>0)
                
                    NSInvocation *invocation = [self.taskArray objectAtIndex:0];


                    if([[NSThread currentThread] isCancelled])
                        break;
                    else if(!([[NSThread currentThread] isCancelled]) && [self     
                 respondsToSelector:invocation.selector ])
                    

                        [invocation invokeWithTarget:self];
                        [self.taskArray removeObjectAtIndex:0];
                    

                
            
            @catch (NSException *exception) 
                NSLog(@"Exception %@", exception.debugDescription);
            
            @finally 

            
        
    

  
    // Save Moc.
    if([[[self managedObjectContext] insertedObjects] count]>0 || ([[[self 
             managedObjectContext] updatedObjects] count]>0) )
    

        NSError *error = nil;
        if(![[self managedObjectContext] save:&error])
        
            NSLog(@"Moc Save Error %@", [error localizedDescription]);
        

    

     [self.managedObjectContext reset];

  

我的二级线程中的saveContext方法如下:

-(void) saveContext


if(!thread_ || [thread_ isCancelled])

    // Clean up cache here  //
    return;



_/* --- Here i have code that iterates through the properties that were received from the network and stored in a cache of NSDictionary data type */_ 


// Now i save the threaded MOC.

if([[[self managedObjectContext] insertedObjects] count]>0 || ([[[self managedObjectContext] updatedObjects] count]>0) )


    NSError *error = nil;
    NSLog(@"We are merging the changes now.. [%d], [%d]", [[[self managedObjectContext] insertedObjects] count], [[[self managedObjectContext] updatedObjects] count]);
    if(![[self managedObjectContext] save:&error])
    
        NSLog(@"Moc Save Error %@", [error localizedDescription]);
    



    _// I create a dispatch queue here to repeteadly call this method. This serves as a hear-beat dispatch queue that checks for updated/inserted objects every 5 seconds._

    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_queue_t queue = dispatch_queue_create("com.myCompany.mocSaveQ", 0);
    dispatch_after(popTime, queue, ^(void)
    
                       // Save Moc.
                       NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(saveContext)]];
                       invocation.selector = @selector(saveContext);
                       invocation.target = self;

                       [self addTask:invocation];

    );


    dispatch_release(queue);


现在,在调试器中,我尝试在线程/第二 MOC 上调用 [NSManagedObjectContext save] 之前在线程中打印更新的对象,并且我确实看到了应用于该托管对象的相应属性的更改。 但是,当我尝试在我的主线程中为 contextDidSave: 方法中的更新对象打印通知对象的 userInfo 字典时,尽管它列出了相同的对象,但只有一个在线程中更新的属性没有新值。这让我很困惑。

更新 2

这里有更多代码可以帮助更好地理解它。

在我的主线程中

数据字典是一个包含属性和子字典的 NSDictionary

-(void) processData:(NSDictionary*) data
 
      for(NSString *key in data)
      
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        fetchRequest.entity = [NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:self.managedObjectContext];
    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"lookupKey = %@", key];
    NSArray *fetchResults = nil;
    NSError *error = nil;

    @synchronized(@"fetch_request_for_lookupKey")
    
        @try
        
            fetchResults = [moc executeFetchRequest:fetchRequest error:&error];
        
        @catch (NSException *exception)
        
            NSLog(@"Exception %@ when executing fetch %@", exception, NSStringFromSelector(_cmd));
        
        @finally
        
            [fetchRequest release];
        
    


            NSManagedObject* myManagedObject = fetchResults.lastObject;
            // We get the dictionary here
            NSDictionary *dictionary = [taskDictionary objectForKey:lookUpKey];

            for(NSString *subKey in dictionary)
            

                 if([key isEqualToString:@"name"])
                 
                        myManagedObject.name = [dictionary objectForKey:subKey];
                 
                 else if([key isEqualToString:@"phone"]
                    //....Update the value...//      


            
      
 

现在在我的辅助线程类中,我在辅助线程的 saveContext 方法中有上面列出的数据处理逻辑。如果您在上面看到,那么我会保存我的主线程 MOC,然后才在我的线程中执行 NSFetchRequest。我还确保在执行保存之后和在我的辅助线程中执行 NSFetchRequest 之前,主 MOC 中没有更新的对象。

【问题讨论】:

祝 Apple 工程师回答您的问题好运,伙计。除非@davedelong 愿意给你一些时间;) 也许你可以上传一个示例项目/要点清楚地说明问题。 大声笑..抱歉在这里提到苹果工程师。实际上,我从 devforums.apple.com 复制并粘贴了我自己的问题。我昨天发了,还没收到回复。因此,我决定把它放在这里来扩大我的读者群:)。代码实际上非常简单。但我会发布一些 sn-ps 以获得一个想法。 @user598789 代码 sn-ps 总是很有帮助。 从您的描述中看不出这个问题的明显原因,显示一些代码。 【参考方案1】:

这是我在文档中找到的:

并发

Core Data 使用线程(或序列化队列)限制来保护托管对象和托管对象上下文(请参阅“Core Data 的并发性”)。这样做的结果是,上下文假定默认所有者是分配它的线程或队列——这由调用其 init 方法的线程确定。因此,您不应该在一个线程上初始化上下文,然后将其传递给另一个线程。相反,您应该将引用传递给持久存储协调器,并让接收线程/队列创建一个从中派生的新上下文。如果使用 NSOperation,则必须在 main(对于串行队列)或 start(对于并发队列)中创建上下文。

在 OS X v10.7 及更高版本和 ios v5.0 及更高版本中,当您创建上下文时,您可以指定将使用它的并发模式(请参阅 initWithConcurrencyType:)。

因此,正如它所说,我建议使用这种方法来获取 MOC 对象:

- (id)initWithConcurrencyType:(NSManagedObjectContextConcurrencyType)ct;

【讨论】:

以上是关于一个线程中对 MOC 属性的更改未传播到主线程的主要内容,如果未能解决你的问题,请参考以下文章

核心数据锁因为合并?

CoreData prepareForDeletion 调用无限次

NSManagedObjectContexts 和多线程

wpf中 我新开一个线程添加控件到主窗体

处理业务:事件是如何在 pipeline 中传播的

核心数据更改不合并