【问题标题】:How do I avoid app termination due to memory issue?如何避免由于内存问题导致应用程序终止?
【发布时间】:2017-11-16 09:29:39
【问题描述】:

我正在从 Web 服务获取 700.000 行,我的应用程序在由于内存问题而终止一段时间后崩溃,它在崩溃之前消耗了大约 1 GB 的内存,代码相当简单,我获取 JSON,我放入一个数组,我循环数组并插入核心数据,完成后我保存上下文

代码如下所示

+ (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:5443/api/till/tilldatav2/%d?StartAtRow=%d&TakeNoOfRows=%d",tillId, startAtRow, takeNoOfRows];

    [[[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];

                                             //NSManagedObjectContext *context =
                                             //appDelegate.persistentContainer.viewContext;
                                             //context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;

                                             NSPersistentContainer *container = appDelegate.persistentContainer;

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

                                                 NSDictionary *tillBasicDataDict = Nil;

                                                 //Loop through the array and for each dictionary insert into local DB
                                                 // lets work on concurrency here
                                                 for (id element in tillBasicDataArray){
                                                     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"];

                                                     // Use Core Data FMD


                                                     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;
}

如下循环遍历数组

+(void)fetchPricelistAll:(int)pricelistId :(int)startAtRow :(int)takeNoOfRows;
{
    if ([NWTillHelper isDebug] == 1) {
        NSLog(@"WebServices:fetchPriceList:priceListId = %d", pricelistId);
    }

    NSString *finalURL = [NSString stringWithFormat:@"https://host.domain.com:5443/api/till/tillpricelistv2/%d?StartAtRow=%d&TakeNoOfRows=%d",pricelistId, startAtRow, takeNoOfRows];

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

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

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

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

                                             NSPersistentContainer *container = appDelegate.persistentContainer;

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

                                             for(NSArray *batch in arrayOfArrays) {

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


                                                     NSDictionary *priceListObjectDict;

                                                     //Loop through the array and for each dictionary insert into local DB
                                                     for (id element in batch) {
                                                         priceListObjectDict = element;

                                                         NSString *currencyName = [priceListObjectDict objectForKey:@"currencyName"];
                                                         NSString *price = [priceListObjectDict objectForKey:@"price"];
                                                         NSString *priceIncTax = [priceListObjectDict objectForKey:@"priceIncTAX"];
                                                         NSString *validFrom = [priceListObjectDict objectForKey:@"validFromDate"];
                                                         NSString *validTo = [priceListObjectDict objectForKey:@"validToDate"];
                                                         NSString *itemId = [priceListObjectDict objectForKey:@"itemID"];

                                                         NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
                                                         [dateFormat setDateFormat:@"YYYY-MM-dd'T'HH:mm:ss"];
                                                         NSDate *validToDate = [dateFormat dateFromString:validTo];
                                                         NSDate *validFromDate = [dateFormat dateFromString:validFrom];

                                                         if([NWTillHelper isDebug] == 1) {
                                                             NSLog(@"WebServices:fetchPriceList:validToDate: >>>> %@ <<<<", validToDate);
                                                             NSLog(@"WebServices:fetchPriceList:validFromDate: >>>> %@ <<<<", validFromDate);
                                                         }

                                                         if([NWTillHelper isDebug] == 1) {
                                                             NSLog(@"PimItemListView:tableView:context = %@", context);
                                                         }

                                                         NSManagedObject *newPrlItem = Nil;
                                                         newPrlItem = [NSEntityDescription
                                                                       insertNewObjectForEntityForName:@"PriceList"
                                                                       inManagedObjectContext:context];

                                                         [newPrlItem setValue:itemId forKey:@"itemId"];
                                                         [newPrlItem setValue:validToDate forKey:@"validTo"];
                                                         [newPrlItem setValue:validFromDate forKey:@"validFrom"];
                                                         [newPrlItem setValue:price forKey:@"price"];
                                                         [newPrlItem setValue:priceIncTax forKey:@"priceIncTax"];
                                                         [newPrlItem setValue:currencyName forKey:@"currencyName"];

                                                         if ([NWTillHelper isDebug] == 1) {
                                                             NSLog(@"WebServices:fetchTillData:ItemId in loop = %@", itemId);
                                                             NSLog(@"WebServices:fetchTillData:newPrlItem = %@", newPrlItem);
                                                             NSLog(@"WebServices:fetchTillData: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:@"hasPriceList"];
                                                         [tillUserDefaults synchronize];
                                                     }
                                                 }];
                                             }
                                         }
                                     }
                                 }] resume];
}

它仍然增加到 2 GB 并被终止,当 NSURLSession 完成块达到内存使用量约为 250 时,我假设是在它将整个数据集下载到 NSArray 之后,但在那之后,当我想写入核心时数据全部出错,达到 2 GB 并被终止

为什么会这样?

【问题讨论】:

  • 通过分页获取更少的数据。
  • 我不能像刚才所说的那样获取更少的数据,请您详细说明
  • 添加查询限制。 (偏移量和限制)
  • 我已经有了,但在这种情况下,我如何循环并发送多个任务?因为我最终会遇到线程问题,不是吗?你有一些指向任何文档或任何提示的指针吗?

标签: objective-c core-data


【解决方案1】:

首先将您的大数组拆分为数组数组。尝试一个不太大(应用程序会崩溃)或太小(需要很长时间)的批量大小。我建议从 500 开始。请参阅此处如何操作:What is an easy way to break an NSArray with 4000+ objects in it into multiple arrays with 30 objects each?。我假设您可以将该代码转换为数组扩展名..

    NSArray *arrayOfArrays = [tillBasicDataArray splitIntoArrayBatchSize:500];

接下来,您可以将多个块排入队列,每个块将只处理多个数组中的一个并在最后保存。

    for(NSArray *batch in arrayOfArrays){
         [container performBackgroundTask:^(NSManagedObjectContext *context ) {
          ...
          for (id element in batch){
          ...

每个 performBackgroundTask 都被排入一个内部操作,并且一次处理一个。

其余代码基本保持不变。

【讨论】:

  • 这没什么区别,下载 originalArray 后的内存使用量约为 250 MB,但是当它开始循环并将其放入核心数据时,它很快达到 2 GB 并崩溃了正在将我的新代码添加到原始问题中,我现在将其拆分为一个或多个数组,但这没有区别
  • 这里有一个很好的解决方案:stackoverflow.com/questions/15932492/….
  • 我已经在这样做了,拆分 API 并分批下载等,问题是当我处理批次时它们都是并行运行的,我得到 300 个批次试图处理 1000 个对象并放入核心数据,我不知道如何使用操作队列或其他机制来阻止它,我尝试在处理每个批次的 for 循环中添加队列块,但没有奏效,所以我一定做错了什么跨度>
  • 我怀疑即使没有核心数据,仅解析如此大量的数据也会有问题。您可以尝试将每个数组保存到磁盘上的文件中,看看它是否仍然内存不足。 (如果确实如此,则可能表明内存问题发生在核心数据之前)
  • 同样的问题,如何确保批量处理?我是否像你说的那样保存到文件或核心数据可能无关紧要,我必须确保我一个一个地处理数组,所以我给自己一个机会来释放两者之间的资源,这就是我卡住的地方我正在创建 arrayOfArrays部分工作正常,之后在我的 for 循环中,我不明白如何在处理批处理中的下一个数组之前序列化处理和释放资源
猜你喜欢
  • 2021-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-05
相关资源
最近更新 更多