应用程序通过 RestKit 和 Magical Record 滞后于巨大的核心数据映射

Posted

技术标签:

【中文标题】应用程序通过 RestKit 和 Magical Record 滞后于巨大的核心数据映射【英文标题】:App lags on huge Core Data mappings via RestKit & Magical Record 【发布时间】:2015-04-08 15:06:52 【问题描述】:

我still trying 来确定加载 UI 线程的内容。在一个类(UITableView 的一个孩子)中有一个 FRC:

 NSFetchRequest *request = [DEPlace MR_requestAllWithPredicate:[NSPredicate predicateWithFormat:@"isWorking == YES"]];

 request.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES] ];
 self.placesController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                                managedObjectContext:[NSManagedObjectContext MR_rootSavingContext]
                                                                  sectionNameKeyPath:nil
                                                                           cacheName:nil];
 self.placesController.delegate = self;

它曾经被附加到一个 MR_contextForCurrentThread。将其更改为 rootSavingContext 会稍微影响性能。然后我将根上下文和默认上下文设置为同一个:

[NSManagedObjectContext MR_setRootSavingContext:managedObjectStore.persistentStoreManagedObjectContext];
[NSManagedObjectContext MR_setDefaultContext:managedObjectStore.persistentStoreManagedObjectContext];

过去的默认上下文设置为 mainQueueManagedObjectContext。我想从字面上移动与背景相关的所有核心数据,让 FRC 负责与 UI 的交互。 FRC 委托通过以下方式获取新数据:

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller

  //self.places = [self sortPlaces:controller.fetchedObjects];

  self.places = controller.fetchedObjects;

  [self.delegate contentUpdatedInDatasource:self];
 

我现在禁用了排序,认为它可能会影响主线程。我试图弄清楚还有什么可以使用 Time Profiler 加载主线程,但没有发现任何可疑之处。 screenshot

加载所有数据后,一切运行顺利,应用程序仅在第一次启动时滞后,此时数据库已填充。由于所有与加载相关的内容都由 RestKit 持有,我认为它不会导致问题。

我正在考虑最多每秒延迟 10 个请求,但不知道如何实现。基本上,在开始时,应用程序会获取 ID 数组(现在约为 250 个),然后循环遍历数组并按每个 ID 从服务器请求数据。到目前为止,这并不是那么重要,但是当阵列增长到 1-2k 时,这将是一个大问题。顺便说一句,单个数据对象在数据库中有 4 个关系。减少依赖是一种可能的解决方案吗?

更新: 我试图将请求拆分为 1 的 1,它导致了一个非常奇怪的行为。 由于某种原因,请求之间存在巨大的延迟。 这就是我获取 ID 数组的方式

        AFJSONRequestOperation *op = [[AFJSONRequestOperation alloc] initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[APIRoot stringByAppendingFormat:@"/venues/listId?%@=%@&%@=%@", TokenKey, [DEUser token], UDIDKey, [DEUser udid]]]]];

        // dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
        dispatch_queue_t backgroundQueue = dispatch_queue_create("com.name.bgqueue", NULL);
        op.successCallbackQueue = backgroundQueue;

        [op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) 
             //gettin an array of IDs
            NSArray *array = (NSArray*) responseObject;
            if(array.count)
            
                _array = array;
                [self getVenuesFromSelfArrayWithCurrentIndex:0];
            
         failure:^(AFHTTPRequestOperation *operation, NSError *error) 
            NSLog(@"3rr0r: %@", error);
        ];

        [[NSOperationQueue mainQueue] addOperation:op];

这是递归方法的代码:

- (void)getVenuesFromSelfArrayWithCurrentIndex: (NSUInteger)index

if(index >= _array.count) NSLog(@"loading finished!"); return; 
//version of the app, location e.t.c.
NSMutableDictionary *options = [[self options] mutableCopy];
[options setObject:[_array objectAtIndex:index] forKey:@"venueId"];
//method below calls RKs getObjectsAtPath, and it's pretty much the only thing it does
[[DEAPIService sharedInstance] getObjectsOfClass:[DEPlace class]
                                     withOptions:options
                                         success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
                                             NSManagedObject *object = [mappingResult.array firstObject];
                                             if([object isKindOfClass:[DEPlace class]])
                                             
                                                 [self getVenuesFromSelfArrayWithCurrentIndex:index+1];
                                             
                                          failure:^(RKObjectRequestOperation *operation, NSError *error)
                                            NSLog(@"Failed to load the place with options: %@", options.description);
                                             [self getVenuesFromSelfArrayWithCurrentIndex:index+1];
                                         ];

奇怪的是,启动下一个请求和 cpu 使用日志和线程看起来需要大约 1-2 秒(!)。奇怪。

Screenshot 1

Screenshot 2

有什么建议吗?

【问题讨论】:

FRC 应该始终附加到主线程上下文 - 它由 UI 使用,因此它不能使用背景上下文(至少不是微不足道的)。所以你在发布时同时发出 250 个请求? 由于我将它附加到 rootSavingContext,因此我猜它现在已附加到 persistentStoreManagedObjectContext。正确,我收到了 250 个启动请求。有时应用程序只是锁定(我以为它已经消失了,但我错了)。我的单个请求是~10-15Kb。 Ofc 我可以将请求分组等等,但我真的需要了解导致这些滞后的原因。 【参考方案1】:

目前我只能建议大约 250 个请求。您不能发出超过 4 或 5 个并发网络请求,而不会使网络泛滥并使其在移动设备上停止运行。确实,您应该更改 Web 服务设计,以便可以发送批处理请求,因为这对客户端和服务器来说都更有效。

无论如何,您可以通过设置对象管理器的operationQueuemaxConcurrentOperationCount 来限制并发请求。建议将其设置为 4。

【讨论】:

嗯。当我调用 RK 的 getObjectsAtPath 时,它会创建一个 RKRequestOperation 吗?其他 246 个请求会被放入队列吗? 是的,它为请求创建操作,为映射创建其他操作。 250 个网络请求并不理想 ;-) 那么我应该考虑减少请求的数量吗?我正在考虑做一些递归来逐个执行请求。 通过批处理更好地减少请求计数。设置和拆除连接的成本很高,因此如果它是后台类型操作且对时间不敏感,则最好将更多内容打包到每个请求中 我自己做了某种“变态批处理”并更新了原始帖子。去检查 RK 的分页器。

以上是关于应用程序通过 RestKit 和 Magical Record 滞后于巨大的核心数据映射的主要内容,如果未能解决你的问题,请参考以下文章

Magical Record 做了哪些 RESTKit 没有做的事情?我会需要这两个框架吗?

使用 Kiwi、Core Data 和 Magical Record 进行单元测试

CoreData + Magical Record 运行选择查询

Magical Record 关系返回空对象

RestKit - 发布或放置包含嵌套实体的实体

RestKit,核心数据,神奇的记录,大量数据和滞后的 UI