NSInternalInconsistencyException при запуске фоновой выборки с циклом
У меня есть этот код, который пытается сделать фоновую выборку для данных HealthKit. Код работает нормально, когда я впервые запускаю приложение, но если я вручную выполняю фоновую выборку (используя команду отладки), я получаю исключение и ошибку, которая говорит reason: 'this request has been neutered - you can't call -sendResponse: twice nor after encoding it'
и я не совсем уверен, почему.
Вот код, который выбирает данные:
- (void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
if (!self.healthStore) {
self.healthStore = [HKHealthStore new];
}
dataTypes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:1], HKQuantityTypeIdentifierStepCount,
[NSNumber numberWithInt:2], HKQuantityTypeIdentifierFlightsClimbed,
[NSNumber numberWithInt:3], HKQuantityTypeIdentifierDistanceWalkingRunning,
[NSNumber numberWithInt:4], HKQuantityTypeIdentifierDistanceCycling, nil];
achievementData = [NSMutableDictionary new];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *startDate = [calendar startOfDayForDate:[NSDate date]];
NSDate *endDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:1 toDate:startDate options:0];
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionNone];
for (NSString *key in dataTypes) {
HKSampleType *sampleType = [HKSampleType quantityTypeForIdentifier:key];
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:predicate limit:HKObjectQueryNoLimit sortDescriptors:nil resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
if (!error) {
if (!results) {
NSLog(@"No results were returned form query");
completionHandler(UIBackgroundFetchResultNoData);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self processNewDataWithResults:results andType:key];
completionHandler(UIBackgroundFetchResultNewData);
});
}
} else {
NSLog(@"Error: %@ %@", error, [error userInfo]);
completionHandler(UIBackgroundFetchResultFailed);
}
}];
[self.healthStore executeQuery:query];
}
}
Затем у меня есть отдельная функция, которая обрабатывает данные, которые вы видите, вызывается при обнаружении результатов. Если вы хотите, я могу вставить это здесь, но это довольно долго, не уверен, имеет ли это какое-либо отношение к этому.
Я попытался установить точки останова, чтобы увидеть, когда вызывается обработчик завершения, но из того, что я могу сказать, он вызывается только один раз, если я не пропустил что-то глупое.
Если у кого-нибудь есть какие-либо советы, пожалуйста, дайте мне знать:) Спасибо!
РЕДАКТИРОВАТЬ Вот как выглядит сообщение об ошибке:
2015-05-13 10:11:54.467 appName[379:169163] *** Assertion failure in -[UIFetchContentInBackgroundAction sendResponse:], /SourceCache/BaseBoard/BaseBoard-98.3/BaseBoard/BSAction.m:221
2015-05-13 10:11:54.470 appName[379:169163] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'this request has been neutered - you can't call -sendResponse: twice nor after encoding it'
*** First throw call stack:
(0x28ed4137 0x36e30c77 0x28ed400d 0x29bd2bc9 0x2dbde865 0x397ed5 0x2dbde7cf 0x2ca82a3d 0x39019b 0x390187 0x393e9d 0x28e99889 0x28e97fa9 0x28de39a1 0x28de37b3 0x305951a9 0x2c56e695 0xdff29 0x373d8aaf)
libc++abi.dylib: terminating with uncaught exception of type NSException
1 ответ
Посмотрев на код, который вы разместили, я не вижу другого способа, кроме того, что обработчик завершения фоновой выборки будет вызван ровно 4 раза из-за for
петля.
Пути кода в resultHandlers
каждого экземпляра HKSampleQuery
в конечном итоге в completionHandler(UIBackgroundFetchResult...)
позвоните в любом случае, и вы всегда создаете четыре экземпляра, так что это утверждение 'you can't call -sendResponse: twice'
жалуется на ИМО.
Должно быть легко проверить, является ли это проблемой, закомментировав 3 запроса от dataTypes
толковый словарь.
Отредактируйте: как было запрошено в комментариях, вот возможное решение (... только от макушки головы, так что возьмите его с крошкой соли..):
Он (a) использует блокировку семафора, чтобы превратить обратный вызов результата асинхронного запроса в "синхронный" вызов, и (b) использует группу диспетчеризации, чтобы дождаться завершения всех 4 запросов перед выполнением обработчика завершения.
// NOTE: This example assumes that the fetch has "new data" if any of the queries returned something
// Also it skips the 'NSError' part (which could work exactly like the 'result' var)
// Define one or more block-global result variables for queries can put their end state into
UIBackgroundFetchResult __block result = UIBackgroundFetchResultNoData;
// Create a dispatch group that will manage all the concurrent queries
dispatch_queue_t queue = dispatch_queue_create([@"my.query.queue" UTF8String], DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t queries = dispatch_group_create();
// For each dataType dispatch a group block containing the query to run on the concurrent queue
for (NSString *key in dataTypes) {
dispatch_group_async(queries, queue, ^{
// To work around the asynchronous callback, I'll use a lock to wait for the query to return their result, so..
// ..like above, a block-global var will hold the result of the query
BOOL __block success = NO;
// ..create a one-time lock..
dispatch_semaphore_t lock = dispatch_semaphore_create(0);
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:predicate limit:HKObjectQueryNoLimit sortDescriptors:nil resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
success = YES; // ..or however you define success.. ;)
dispatch_semaphore_signal(lock); // ..open lock to signal result available..
}];
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); // ..wait for callback result lock to open..
// ..determine & set result.
if (success) {
result = UIBackgroundFetchResultNewData;
}
});
}
// Schedule a final block to execute (on the main queue) when all the other group blocks have finished running
dispatch_group_notify(queries, dispatch_get_main_queue(), ^{
// Determine final result and call completion handler
completionHandler(result);
});