从 NSUrlSession 调用时 CoreData 主线程 UI 警告

CoreData main thread UI warning when called from NSUrlSession

我正在使用 NSUrlSession 下载 JSON 数据,它正在工作,但我收到 UI 警告,AppDelegate can only Be called the main thread 我不确定如何摆脱在这种情况下。我尝试将 AppDelegate 行本身包装到 main_queue 语句中,但这没有任何区别,我假设我在这里做了一些根本性的基本错误,但我无法弄清楚到底是什么。

代码如下所示。

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

    NSString *finalURL = [NSString stringWithFormat:@"https://xxx.yyy.com/zzz/zzz/zzz/%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]);
                                             }

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

                                             NSOperationQueue *prlQueue = [[NSOperationQueue alloc] init];
                                             prlQueue.maxConcurrentOperationCount = 1;

                                             NSPersistentContainer *container = appDelegate.persistentContainer;

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

                                             NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
                                             [dateFormat setDateFormat:@"YYYY-MM-dd'T'HH:mm:ss"];

                                             for(NSArray *batch in arrayOfArrays) {

                                                 [prlQueue addOperationWithBlock:^{

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

                                                         NSDictionary *priceListObjectDict = nil;

                                                         //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"];

                                                             NSDate *validToDate = [dateFormat dateFromString:validTo];
                                                             NSDate *validFromDate = [dateFormat dateFromString:validFrom];

                                                             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];
                                                         }
                                                         [context reset];
                                                     }];
                                                 }];
                                             }
                                         }
                                     }
                                 }] resume];
}

错误消息如下所示

=================================================================
Main Thread Checker: UI API called on a background thread: -[UIApplication delegate]
PID: 43836, TID: 4857183, Thread name: (none), Queue name: NSOperationQueue 0x60c00023aac0 (QOS: UNSPECIFIED), QoS: 0
Backtrace:
4   NWMPos                              0x00000001076ce5c2 __35+[WebServices fetchPricelistAll:::]_block_invoke + 610
5   CFNetwork                           0x000000010f295208 __75-[__NSURLSessionLocal taskForClass:request:uploadFile:bodyData:completion:]_block_invoke + 19
6   CFNetwork                           0x000000010f294a6d __49-[__NSCFLocalSessionTask _task_onqueue_didFinish]_block_invoke + 147
7   Foundation                          0x0000000109a189b7 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7
8   Foundation                          0x0000000109a1881a -[NSBlockOperation main] + 68
9   Foundation                          0x0000000109a16cd6 -[__NSOperationInternal _start:] + 778
10  libdispatch.dylib                   0x000000010ec3d43c _dispatch_client_callout + 8
11  libdispatch.dylib                   0x000000010ec42af4 _dispatch_block_invoke_direct + 592
12  libdispatch.dylib                   0x000000010ec3d43c _dispatch_client_callout + 8
13  libdispatch.dylib                   0x000000010ec42af4 _dispatch_block_invoke_direct + 592
14  libdispatch.dylib                   0x000000010ec42884 dispatch_block_perform + 109
15  Foundation                          0x0000000109a12ce4 __NSOQSchedule_f + 342
16  libdispatch.dylib                   0x000000010ec3d43c _dispatch_client_callout + 8
17  libdispatch.dylib                   0x000000010ec43856 _dispatch_continuation_pop + 967
18  libdispatch.dylib                   0x000000010ec41c86 _dispatch_async_redirect_invoke + 780
19  libdispatch.dylib                   0x000000010ec491f9 _dispatch_root_queue_drain + 772
20  libdispatch.dylib                   0x000000010ec48e97 _dispatch_worker_thread3 + 132
21  libsystem_pthread.dylib             0x000000010f1005a2 _pthread_wqthread + 1299
22  libsystem_pthread.dylib             0x000000010f10007d start_wqthread + 13
2017-10-30 14:40:51.252817+0800 NWMPos[43836:4857183] [reports] Main Thread Checker: UI API called on a background thread: -[UIApplication delegate]
PID: 43836, TID: 4857183, Thread name: (none), Queue name: NSOperationQueue 0x60c00023aac0 (QOS: UNSPECIFIED), QoS: 0
Backtrace:
4   NWMPos                              0x00000001076ce5c2 __35+[WebServices fetchPricelistAll:::]_block_invoke + 610
5   CFNetwork                           0x000000010f295208 __75-[__NSURLSessionLocal taskForClass:request:uploadFile:bodyData:completion:]_block_invoke + 19
6   CFNetwork                           0x000000010f294a6d __49-[__NSCFLocalSessionTask _task_onqueue_didFinish]_block_invoke + 147
7   Foundation                          0x0000000109a189b7 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7
8   Foundation                          0x0000000109a1881a -[NSBlockOperation main] + 68
9   Foundation                          0x0000000109a16cd6 -[__NSOperationInternal _start:] + 778
10  libdispatch.dylib                   0x000000010ec3d43c _dispatch_client_callout + 8
11  libdispatch.dylib                   0x000000010ec42af4 _dispatch_block_invoke_direct + 592
12  libdispatch.dylib                   0x000000010ec3d43c _dispatch_client_callout + 8
13  libdispatch.dylib                   0x000000010ec42af4 _dispatch_block_invoke_direct + 592
14  libdispatch.dylib                   0x000000010ec42884 dispatch_block_perform + 109
15  Foundation                          0x0000000109a12ce4 __NSOQSchedule_f + 342
16  libdispatch.dylib                   0x000000010ec3d43c _dispatch_client_callout + 8
17  libdispatch.dylib                   0x000000010ec43856 _dispatch_continuation_pop + 967
18  libdispatch.dylib                   0x000000010ec41c86 _dispatch_async_redirect_invoke + 780
19  libdispatch.dylib                   0x000000010ec491f9 _dispatch_root_queue_drain + 772
20  libdispatch.dylib                   0x000000010ec48e97 _dispatch_worker_thread3 + 132
21  libsystem_pthread.dylib             0x000000010f1005a2 _pthread_wqthread + 1299
22  libsystem_pthread.dylib             0x000000010f10007d start_wqthread + 13

尝试将 AppDelegate 行中的所有代码包装到 main_queue

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

  NSString *finalURL = [NSString stringWithFormat:@"https://xxx.yyy.com/zzz/zzz/zzz/%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]);
                                   }

                                   dispatch_async(dispatch_get_main_queue(), ^{
                                     AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];

                                     NSOperationQueue *prlQueue = [[NSOperationQueue alloc] init];
                                     prlQueue.maxConcurrentOperationCount = 1;

                                     NSPersistentContainer *container = appDelegate.persistentContainer;

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

                                     NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
                                     [dateFormat setDateFormat:@"YYYY-MM-dd'T'HH:mm:ss"];

                                     for(NSArray *batch in arrayOfArrays) {

                                       [prlQueue addOperationWithBlock:^{

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

                                           NSDictionary *priceListObjectDict = nil;

                                           //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"];

                                             NSDate *validToDate = [dateFormat dateFromString:validTo];
                                             NSDate *validFromDate = [dateFormat dateFromString:validFrom];

                                             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];
                                           }
                                           [context reset];
                                         }];
                                       }];
                                     }
                                   });
                                 }
                               }
                             }] resume];
}

如您所说,对 UIApplication.delegate 的调用被视为 UI 的一部分,因此无法从后台线程访问。

由于您只需要对持久容器的引用,我建议:

  • 在调用 dataTaskWithURL: 之前创建一个指向 [[UIApplication delegate] persistentContainer] 的局部变量,然后在块
  • 中使用该局部变量
  • 或者,您可以重构代码以将持久容器保存在与 UI 无关的不同全局对象上。