将大量数据保存到核心数据会导致应用程序因内存问题而终止

Posted

技术标签:

【中文标题】将大量数据保存到核心数据会导致应用程序因内存问题而终止【英文标题】:Saving a lot of data to core data causes app termination due to memory issue 【发布时间】:2017-06-15 02:22:06 【问题描述】:

我正在将大量项目下载到 NSArray 中,因为我需要将所有项目都保存在本地以供离线使用,一旦将数据下载到 NSURLSession 竞争处理程序中的 NSArray,我的应用程序的内存使用量相当适中大约 120 MB,但是当我开始循环通过这个 NSArray 将数据插入核心数据并将其保存在本地时,内存峰值非常快地达到 2 GB,然后应用程序由于内存问题而终止。我已经尝试将 NSArray 拆分为数组的 NSArray 以尝试在将每个批次保存到磁盘后释放内存,但这也无济于事,所以我必须遗漏一些东西。我究竟做错了什么?我需要保持较低的内存占用空间,以便我可以下载数据,并且它不是延迟加载的选项,只能下载需要的内容等,因为我必须提供离线场景,所以我绝对必须在本地拥有数据。

总之

完成处理程序启动 for 循环时的内存使用量约为 120 MB 然后它迅速增加到 2 GB 并且应用程序被终止 应用程序始终在前台

我在下面附上下载和保存数据的代码

+ (void)fetchTillDataAll:(int)tillId :(int)startAtRow :(int)takeNoOfRows   

    if ([NWTillHelper isDebug] == 1)   
        NSLog(@"WebServices:fetchTillDataAll:tillId = %d, startAtRow = %d, takeNoOfRows = %d", tillId, startAtRow, takeNoOfRows);  
      

    NSString *finalURL = [NSString stringWithFormat:@"https://host.domain.com/api/foo/bar];  

    [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:finalURL]  
                                 completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)   

                                     if (error != nil)   
                                         if ([NWTillHelper isDebug] == 1)   
                                             NSLog(@"WebServices:fetchTillDataAll:Transport error %@", error);  
                                           
                                      else   
                                         NSHTTPURLResponse *responseHTTP;  
                                         responseHTTP = (NSHTTPURLResponse *) response;  

                                         if(responseHTTP.statusCode != 200)   
                                             if ([NWTillHelper isDebug] == 1)   
                                                 NSLog(@"WebServices:fetchTillDataAll:Server Error %d", (int) responseHTTP.statusCode);  
                                               
                                          else   
                                             NSArray *tillBasicDataArray = [NSJSONSerialization JSONObjectWithData:data  
                                                                                                           options:0  
                                                                                                             error:NULL];  
                                             if ([NWTillHelper isDebug] == 1)   
                                                 NSLog(@"WebServices:fetchTillDataAll:tillBasicDataArray count = %lu", (unsigned long)[tillBasicDataArray count]);  
                                                 NSLog(@"WebServices:fetchTillDataAll:tillBasicDataArray looks like %@",tillBasicDataArray);  
                                               

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


                                             NSPersistentContainer *container = appDelegate.persistentContainer;  

                                             NSArray *arrayOfArrays = [NWTillHelper splitIntoArraysOfBatchSize:tillBasicDataArray :1000];  

                                             for(NSArray *batch in arrayOfArrays)   

                                                 [container performBackgroundTask:^(NSManagedObjectContext *context )   
                                                     context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;  

                                                     NSDictionary *tillBasicDataDict = Nil;  

                                                     /  
                                                     /  
                                                     for (id element in batch)  
                                                         tillBasicDataDict = element;  

                                                         NSString *itemId = [tillBasicDataDict objectForKey:@"itemId"];  
                                                         NSString *brandId = [tillBasicDataDict objectForKey:@"companyId"];  
                                                         NSString *languageId = [tillBasicDataDict objectForKey:@"languageCode"];  
                                                         NSString *colorCode = [NSString stringWithFormat:@"%@", [tillBasicDataDict objectForKey:@"colorCode"]];  
                                                         NSString *discountable = [tillBasicDataDict objectForKey:@"discountable"];  
                                                         NSString *exchangeable = [tillBasicDataDict objectForKey:@"exchangeable"];  
                                                         NSString *noos14 = [tillBasicDataDict objectForKey:@"noos14"];  
                                                         NSString *sizeCode = [NSString stringWithFormat:@"%@", [tillBasicDataDict objectForKey:@"sizeCode"]];  
                                                         NSString *taxGroup = [tillBasicDataDict objectForKey:@"taxGroupId"];  
                                                         NSString *taxRegion = [tillBasicDataDict objectForKey:@"taxRegion"];  
                                                         NSString *tradeItemDesc = [tillBasicDataDict objectForKey:@"tradeItemDesc"];  
                                                         NSString *withTax = [tillBasicDataDict objectForKey:@"withTax"];  
                                                         NSString *status = [tillBasicDataDict objectForKey:@"status"];  

                                                         /  


                                                         NSManagedObject *newPimItem = Nil;  
                                                         newPimItem = [NSEntityDescription  
                                                                       insertNewObjectForEntityForName:@"TillData"  
                                                                       inManagedObjectContext:context];  

                                                         [newPimItem setValue:itemId forKey:@"itemId"];  
                                                         [newPimItem setValue:brandId forKey:@"brandId"];  
                                                         [newPimItem setValue:languageId forKey:@"languageCode"];  
                                                         [newPimItem setValue:colorCode forKey:@"colorCode"];  
                                                         [newPimItem setValue:discountable forKey:@"discountable"];  
                                                         [newPimItem setValue:exchangeable forKey:@"exchangeable"];  
                                                         [newPimItem setValue:noos14 forKey:@"noos14"];  
                                                         [newPimItem setValue:sizeCode forKey:@"sizeCode"];  
                                                         [newPimItem setValue:[NSNumber numberWithInt:[taxGroup intValue]] forKey:@"taxGroup"];  
                                                         [newPimItem setValue:taxRegion forKey:@"taxRegion"];  
                                                         [newPimItem setValue:tradeItemDesc forKey:@"tradeItemDesc"];  
                                                         [newPimItem setValue:[NSNumber numberWithInt:[withTax intValue]] forKey:@"withTax"];  
                                                         [newPimItem setValue:[NSNumber numberWithInt:[status intValue]] forKey:@"status"];  

                                                         if ([NWTillHelper isDebug] == 1)   
                                                             NSLog(@"WebServices:fetchTillDataAll:ItemId in loop = %@", itemId);  
                                                             NSLog(@"WebServices:fetchTillDataAll:newPimItem = %@", newPimItem);  
                                                             NSLog(@"WebServices:fetchTillDataAll:CoreData error = %@", error);  
                                                           

                                                       
                                                     NSError *error = nil;  
                                                     if (![context save:&error])   
                                                         NSLog(@"Failure to save context: %@\n%@", [error localizedDescription], [error userInfo]);  
                                                         abort();  
                                                      else   
                                                         NSUserDefaults *tillUserDefaults = [NSUserDefaults standardUserDefaults];  
                                                         [tillUserDefaults setInteger:1 forKey:@"hasTillData"];  
                                                         [tillUserDefaults synchronize];  
                                                       
                                                 ];  
                                               
                                           
                                       
                                 ] resume];  
  

NSArray 的拆分方法如下所示

+ (NSArray *) splitIntoArraysOfBatchSize:(NSArray *)originalArray :(int)batchSize   

    NSMutableArray *arrayOfArrays = [NSMutableArray array];  

    for(int j = 0; j < [originalArray count]; j += batchSize)   

        NSArray *subarray = [originalArray subarrayWithRange:NSMakeRange(j, MIN(batchSize, [originalArray count] - j))];  
        [arrayOfArrays addObject:subarray];  
      

    return arrayOfArrays;  
 

---- 编辑----

我什至尝试将下载分成 1000 条记录的批次,但这也无济于事。

while ( loopCount < numberOfLoops ) 
            if([NWTillHelper isDebug] == 1) 
                NSLog(@"%s loopCount = %d", __PRETTY_FUNCTION__, loopCount);
                NSLog(@"%s startAtRow = %d", __PRETTY_FUNCTION__, startAtRow);
                NSLog(@"%s records to fetch = %@", __PRETTY_FUNCTION__, recordsToFetchStr);
            

        //[self fetchPricelistAll:(int)[NWTillHelper getPricelist] :0 :recordsToFetch];
        [self fetchPricelistAll:(int)[NWTillHelper getPricelist] :startAtRow :batchSizeInt];

        startAtRow = startAtRow + batchSizeInt;

        loopCount++;
    

【问题讨论】:

我也尝试过使用@autoreleasepool,但并没有改善这种情况 【参考方案1】:

我建议拆分 Web 服务响应以按位提供数据。您可以从服务器下载位并将它们保存为文档文件夹中的文本文件。从文本文件中一个接一个地获取数据并将其保存在核心数据中。保存到核心数据完成后删除文件。

【讨论】:

我试过分批加载数据,1000条记录,但还是没关系,问题依旧,必须有办法在每批完成后释放内存,保存到txt文件听起来像一个非常丑陋的黑客,在批量大小的每个循环之后为什么没有释放内存? 我看到您同时将所有数据同时保存到核心数据中。如果您一次将一批保存到核心数据上会怎样。这将减少内存打印。 已经在每批之后保存,每批有1000条记录,而且已经保存在后台块中,所以我认为这意味着它在每批之后保存,你是说不是这样吗?跨度> 根据我对代码的快速回顾,当您使用 performbackgroundtask 方法时,您的 1000 个记录批次将尝试同时保存到核心数据。 真的吗?我不认为持久化容器应该根据 SO 上的其他答案在内部串行队列中执行每个块,并且我没有遇到锁定问题【参考方案2】:

我们可以按照以下方法进行

    创建 100/1000 条记录后在上下文中调用 save 方法。

    保存后我们可以调用reset方法一个上下文

    [managedObjectContext 重置];

【讨论】:

以上是关于将大量数据保存到核心数据会导致应用程序因内存问题而终止的主要内容,如果未能解决你的问题,请参考以下文章

将更改的图像保存到相机胶卷时因内存问题而终止

尝试将纬度和经度保存到核心数据会导致访问错误

应用程序扩展“因内存问题而终止”

核心数据和大文件下载

应用程序因核心数据的一个属性而崩溃

Java前提下, MySQL数据库,一次性存储大量数据导致内存溢出