【问题标题】:Background thread method not solving freezing issue后台线程方法无法解决冻结问题
【发布时间】:2015-05-21 04:08:15
【问题描述】:

我有我的应用程序冻结问题,所以我使用 Instruments 查找问题,发现问题与 CoreData 保存和获取有关。我已经尝试过后台 coredata 方法(父子,通知),但我的问题还没有解决。我还提到了http://martiancraft.com/blog/2015/03/core-data-stack/,但不知道这个方法是如何在我的应用程序中实现的。

仪器日志: https://www.dropbox.com/s/agjtw1wqubsgwew/Instruments9.trace.zip?dl=0

保存到数据库

  -(void)updateThreadEntityWithSyncDetails:(NSMutableDictionary *)inDictionary
  {

 NSString *loginUser=[[NSUserDefaults standardUserDefaults] valueForKey:@"currentUser"];

 AppDelegate *sharedDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [sharedDelegate managedObjectContext];

  //    NSManagedObjectContext *writerContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
  //   [writerContext setPersistentStoreCoordinator:[sharedDelegate persistentStoreCoordinator]];

 // create main thread MOC
//    context = [[NSManagedObjectContext alloc]        initWithConcurrencyType:NSMainQueueConcurrencyType];
    //    context.parentContext = writerContext;

   // NSManagedObjectContext *contextforThread = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

  // contextforThread.parentContext = context;

 // [contextforThread performBlock:^{




NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"ThreadInfo"
                                          inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *userPredicate = [NSPredicate predicateWithFormat:@"userEmail == %@",loginUser];
NSPredicate *threadPredicate = [NSPredicate predicateWithFormat:@"threadID == %@",[inDictionary valueForKey:@"thread"]];
NSPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates: @[userPredicate, threadPredicate]];
[fetchRequest setPredicate:compoundPredicate];

NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:nil];
for (ThreadInfo *threadInfo in fetchedObjects)
{
    if([[inDictionary allKeys] containsObject:@"userEmail"])
    {
        if([inDictionary valueForKey:@"userEmail"]!=[NSNull null])
        {
            threadInfo.userEmail=[inDictionary valueForKey:@"userEmail"];
        }
    }
    if([[inDictionary allKeys] containsObject:@"badgeValue"])
    {
        if([inDictionary valueForKey:@"badgeValue"]!=[NSNull null])
        {
            threadInfo.badgeValue=[inDictionary valueForKey:@"badgeValue"];
        }
    }


    if([[inDictionary allKeys] containsObject:@"choice4Percentage"])
    {
        if([inDictionary valueForKey:@"choice4Percentage"]!=[NSNull null])
        {
            threadInfo.choice4Percentage=[inDictionary valueForKey:@"choice4Percentage"];
        }
    }
    if([[inDictionary allKeys] containsObject:@"choice5Percentage"])
    {
        if([inDictionary valueForKey:@"choice5Percentage"]!=[NSNull null])
        {
            threadInfo.choice5Percentage=[inDictionary valueForKey:@"choice5Percentage"];
        }
    }

 }

    NSError *error;
    if(![context save:&error]) {
        NSLog(@"Child error : %@",error);

    }

   // [context performBlock:^{
   //     NSError *error;
   //     if(![context save:&error]) {
   //         NSLog(@"%@",error);
   //     }
   //            }];
   // }];


 }    

获取

-(ThreadInfo *)retrieveSolicitationInfoForThreadID:(NSString*)inThreadID;
 {
NSString *loginUser=[[NSUserDefaults standardUserDefaults] valueForKey:@"currentUser"];

AppDelegate *sharedDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [sharedDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"ThreadInfo"
                                          inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *userPredicate = [NSPredicate predicateWithFormat:@"userEmail == %@",loginUser];
NSPredicate *threadPredicate = [NSPredicate predicateWithFormat:@"threadID == %@",inThreadID];
\
NSPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates: @[userPredicate, threadPredicate]];

[fetchRequest setPredicate:compoundPredicate];

NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:nil];
if(fetchedObjects.count!=0)
{
    ThreadInfo *threadInfo=[fetchedObjects objectAtIndex:0];
    return threadInfo;
}
return nil;
}  

同步

-(void)updateSolicitationWithSyncDetails:(NSDictionary *)inDictionary
 {    
NSMutableDictionary *paramDict=[NSMutableDictionary dictionaryWithDictionary:inDictionary];
NSString *userEmail=[[NSUserDefaults standardUserDefaults] valueForKey:@"currentUser"];

[paramDict setObject:[NSNumber numberWithBool:NO] forKey:@"isSystemMessage"];
[paramDict setObject:userEmail forKey:@"userEmail"];

if([[inDictionary allKeys] containsObject:@"owned"])
{
   BOOL isDuplicate=[[IXDataBaseManager sharedNetworkDataManager] checkForExistenceOfThreadDetailsForSolicitationID:[inDictionary objectForKey:@"solicitation"]];// FETCH
    if(!isDuplicate)
    {
        int randomIndex=[[IXNetworkDataManager sharedNetworkDataManager] getIndexForColorImageForTab:@"OUT"];
        [paramDict setObject:message forKey:@"threadDescription"];
        [paramDict setObject:[NSNumber numberWithInt:randomIndex] forKey:@"colorCode"];

     BOOL isDuplicateVal=[[IXDataBaseManager sharedNetworkDataManager] checkForExistenceOfSolicitationID:[inDictionary objectForKey:@"solicitation"]];// FETCH
   [paramDict setObject:message forKey:@"threadDescription"];

  ThreadInfo *threadInfo=[[IXDataBaseManager sharedNetworkDataManager] retrieveSolicitationInfoForThreadID:[inDictionary objectForKey:@"solicitation"]];
  [paramDict setObject:threadInfo.threadID forKey:@"thread"];
  [[IXDataBaseManager sharedNetworkDataManager] updateThreadEntityWithSyncDetails:paramDict];
    }
}
}

【问题讨论】:

    标签: ios multithreading core-data


    【解决方案1】:

    首先,感谢您立即提供代码 sn-ps 和跟踪。这很有帮助。

    所以,查看跟踪和代码。

    -updateThreadEntityWithSyncDetails: 在主线程上被调用,33% 的时间都花在了那里。不好。

    您将 nil 传递给 error: 永远不要这样做。始终传递错误指针并检查调用结果以查看是否有错误。

    这个条件可以更简洁:

    if([[inDictionary allKeys] containsObject:@"userEmail"])
    {
        if([inDictionary valueForKey:@"userEmail"]!=[NSNull null])
        {
            threadInfo.userEmail=[inDictionary valueForKey:@"userEmail"];
        }
    }
    

    考虑:

    if (inDictionary[@"userEmail"] != nil && inDictionary[@"userEmail"] != [NSNull null]) {
        threadInfo.userEmail = inDictionary[@"userEmail"];
    }
    

    更容易阅读。

    此方法的重写将使工作脱离主线程:

    - (void)updateThreadEntityWithSyncDetails:(NSMutableDictionary*)inDictionary
    {
        NSString *loginUser = [[NSUserDefaults standardUserDefaults] valueForKey:@"currentUser"];
    
        AppDelegate *sharedDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    
        NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [context setParentContext:[sharedDelegate managedObjectContext]];
    
        [context performBlock:^{
            NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"ThreadInfo"];
            [fetchRequest setReturnsObjectsAsFaults:NO];
            [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"userEmail == %@ && threadID == %@",loginUser, inDictionary[@"thread"]];
    
            NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:nil];
            for (ThreadInfo *threadInfo in fetchedObjects) {
                if (inDictionary[@"userEmail"] != nil && inDictionary[@"userEmail"] != [NSNull null]) {
                    threadInfo.userEmail = inDictionary[@"userEmail"];
                }
                if (inDictionary[@"badgeValue"] != nil && inDictionary[@"badgeValue"] != [NSNull null]) {
                    threadInfo.badgeValue = inDictionary[@"badgeValue"];
                }
                if (inDictionary[@"choice4Percentage"] != nil && inDictionary[@"choice4Percentage"] != [NSNull null]) {
                    threadInfo.choice4Percentage = inDictionary[@"choice4Percentage"];
                }
                if (inDictionary[@"choice5Percentage"] != nil && inDictionary[@"choice5Percentage"] != [NSNull null]) {
                    threadInfo.choice5Percentage = inDictionary[@"choice5Percentage"];
                }
            }
    
            NSError *error = nil;
            if (![context save:&error]) {
                NSLog(@"Child error : %@",error);
    
            }
        }];
    }
    

    但是它可能仍会阻塞主线程,因为我猜测大部分 CPU 时间都用于执行此提取。获取速度很慢。您在该提取中有两个字符串比较。那很糟糕,应该纠正。如果 ThreadID 不是字符串,则反转获取。否则,这只是糟糕的数据模型设计,除了修复该提取之外没有太多帮助。

    你的另一个慢点是-checkForExistenceOfThreadDetailsForThreadID:。您没有发布该方法,但我怀疑这是同一个问题,您的 fetch 花费了您大量的时间并且它在主队列中。

    总体上这个设计很差,需要返工。您正在比较数据存储中的字符串,这是检索数据的最慢方法之一。对于似乎不需要在主线程上的事情,您也在主线程上。

    记住黄金法则,如果数据没有被用户操作,那么操作不能在主队列上。没有例外。

    【讨论】:

      【解决方案2】:

      您是否尝试过使用单独的上下文进行操作,并在保存后将其与 mainContext 合并?

      例如

      NSManagedObjectContext * localContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
      [localContext setPersistentStoreCoordinator:[[self managedObjectContext] persistentStoreCoordinator]];
      

      导入完成后保存localContext

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

      这会将您的数据写入 PersistentStore。

      现在您的 mainContext 需要了解 PersistentStore 中的更改。这是通过观察通知来完成的。

      例如

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

      这是对通知做出反应的方法:

      - (void)didSaveNotification:(NSNotification *)notification
      {
          //    NSLog(@"%s %@\n",__PRETTY_FUNCTION__,[notification userInfo]);
          NSManagedObjectContext * mainContext = [self managedObjectContext];
          void (^mergeChanges)(void)=^{
              [mainContext mergeChangesFromContextDidSaveNotification:notification];
          };
          if([NSThread isMainThread]) {
              mergeChanges();
          } else {
              dispatch_sync(dispatch_get_main_queue(), mergeChanges);
          }
      }
      

      使用不同的 NSManagedContext 处理同一个 PersistentStore 比使用单个 NSManagedContext 更线程安全。

      【讨论】:

      【解决方案3】:
       //Your Parent context init
      
      - (NSManagedObjectContext *)managedObjectContext
      {
      if (_managedObjectContext != nil) {
          return _managedObjectContext;
      }
      
      NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
      if (coordinator != nil) {
          _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
          [_managedObjectContext setPersistentStoreCoordinator:coordinator];
           [_managedObjectContext setUndoManager:nil];
      }
      return _managedObjectContext;
       }
      
      - (NSManagedObjectContext *)childManagedObjectContext 
      {
      if (_childManagedObjectContext != nil) {
          return _childManagedObjectContext;
      }
      
      NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
      if (coordinator != nil) {
          _childManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
          [_childManagedObjectContext setPersistentStoreCoordinator:coordinator];
          [_childManagedObjectContext setUndoManager:nil];
             [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_mocDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:nil];
      }
      
      
      return _childManagedObjectContext;
      }
      - (void)_mocDidSaveNotification:(NSNotification *)notification
         {
      NSManagedObjectContext *savedContext = [notification object];
      
      // ignore change notifications for the main MOC
      if (_managedObjectContext == savedContext)
      {
          return;
      }
      
      if (_managedObjectContext.persistentStoreCoordinator != savedContext.persistentStoreCoordinator)
      {
          // that's another database
          return;
      }
      
      dispatch_sync(dispatch_get_main_queue(), ^{
          [_managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
      });
       }
      

      //父子上下文的使用方式

      [UIAppDelegate.childManagedObjectContext performBlock:^{
      
      
               for (int i=0; i<count; i++) {
                   //Fill Database and save it in child context first for each entry and save it in child context
                   [UIAppDelegate.childManagedObjectContext save:nil];
              }
      
              dispatch_sync(dispatch_get_main_queue(), ^{
      
               //Finally when u have inserted all entries save in the parent context
                  [UIAppDelegate.managedObjectContext save:nil];
      
      
      
              });
      
          }]
      

      //你的代码很庞大,很难理解逻辑,但如果你还没有尝试过这种方式,那就试试吧

      【讨论】:

      • 可能是使用两个上下文的问题,如果我使用三个上下文会怎样? workContext-Maincontext-ChildContext 。你知道如何实现这个吗?
      • 我已经尝试过这种方法,但应用程序仍然冻结。在保存之前有一些获取,这也会导致问题,您可以查看我的分析器日志。我如何有效地使用两者?
      • floriankugler.com/2013/04/02/the-concurrent-core-data-stack 是一篇很好的文章,其中有解释。另外我已经编辑了代码,请检查
      • 在你的代码中为什么要两次保存mainObjectcontext?如果我们已经在使用通知,那么为什么我们需要 [UIAppDelegate.managedObjectContext save:nil]; ?
      • 对不起,你不需要通知部分。你可以删除它
      猜你喜欢
      • 2017-09-01
      • 2011-01-28
      • 2021-12-18
      • 2017-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-05
      • 1970-01-01
      相关资源
      最近更新 更多