2016-02-16 2 views
1

Итак, я пытаюсь получить свое приложение для чтения в HealthKit данных. У меня есть функция, которую я вызываю из главного контроллера представления приложения, который вызывает запрос в другом классе для всех данных о работоспособности в этом месяце. Затем выполняется несколько расчетов до того, как массив данных будет возвращен из отдельной функции в классе вычислений, в отдельную функцию в контроллере представления.Выполнение нескольких кодов много раз асинхронно с разными переменными

Запросы занимают около 2 секунд каждый из-за объема данных. Я хотел бы иметь возможность настроить их асинхронно, и когда все они вернутся, я могу обновить интерфейс.

Проблема заключается в том, что я вызываю функцию для каждого месяца, которая идет и запускает HKSampleQueries, но они не возвращаются в порядке, и время, которое требуется для их возврата, варьируется. Это означает, что в конечном итоге переменные меняются на полпути через один набор вычислений данных, потому что следующий набор только что начался.

Я знаю только два пути вокруг этого:

Установить задержку перед вызовом каждого вычисления так:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{} 

Но это время отходы приложение

Или я мог бы просто дублировать код несколько раз и вызывать разные классы для каждого месяца. Но это кажется глупым и неэффективным.

Так что вопрос есть. Как эффективно использовать код, который будет выполняться несколько раз с разными переменными каждый раз. Приветствия

Пример функции:

В контроллере View:

HeartRateCalculator *commonClassTwo =[[HeartRateCalculator alloc] init]; 
[commonClassTwo calculateData:0]; 
[commonClassTwo calculateData:-1]; 
[commonClassTwo calculateData:-2]; 

В HeartRateCalculator

-(void)calculateData:(NSInteger)monthsBack{ 
    //Some other stuff 
//Generate monthPeriodPredicate based on monthsBack integer 
    HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:heartRate predicate:monthPeriodPredicate limit:200000 sortDescriptors:@[timeSortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) { 
    //Finish Calculations, call other functions (ie. [self doThis];) and then return 
//When calculations return, monthPeriodPredicate is always value of the last predicate to be called, not the one that the HKSampleQuery was made with. 
} 
[healthStoreFive executeQuery:query]; 

Полный код:

-(void)calculateData:(NSInteger)monthsBack withCompletionBlock:(void(^)())completionBlock {//0 Means only current month, 2 means this month and last month and month before 
//for(NSInteger i=0; i>=monthsBack; i--){ 
    //monthForCalculation = monthsBack; 
    NSDateComponents *components = [[NSCalendar currentCalendar] components: NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond fromDate:[NSDate date]]; 
    NSDateComponents *adjustableComponent = [[NSDateComponents alloc] init]; 
    [adjustableComponent setMonth:monthsBack]; 
    [adjustableComponent setDay:-[components day]+1]; 
    [adjustableComponent setHour:-[components hour]]; 
    [adjustableComponent setMinute:-[components minute]]; 
    [adjustableComponent setSecond:-[components second]]; 
    startOfMonth = [[NSCalendar currentCalendar] dateByAddingComponents:adjustableComponent toDate:[NSDate date] options:0]; 
    adjustableComponent = [[NSDateComponents alloc] init]; 
    [adjustableComponent setMonth:1]; 
    NSDate *endOfMonth = [[NSCalendar currentCalendar] dateByAddingComponents:adjustableComponent toDate:startOfMonth options:0]; 

    NSDate *secondEarlier = [endOfMonth dateByAddingTimeInterval:-1]; 
    components = [[NSCalendar currentCalendar] components: NSCalendarUnitDay fromDate:secondEarlier]; 
    daysInMonth = [components day]; 

    NSPredicate *monthPeriodPredicate = [HKQuery predicateForSamplesWithStartDate:startOfMonth endDate:endOfMonth options:HKQueryOptionStrictStartDate]; 
    healthStoreFive = [[HKHealthStore alloc] init]; 
    HKQuantityType *heartRate = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate]; 
    NSSortDescriptor *timeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:NO]; 
    HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:heartRate predicate:monthPeriodPredicate limit:200000 sortDescriptors:@[timeSortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) { 
     NSMutableArray *dataValues = [[NSMutableArray alloc] init]; 
     NSMutableArray *dataDates = [[NSMutableArray alloc] init]; 
     for (HKQuantitySample *sample in results) { 
      [dataValues addObject:[NSNumber numberWithFloat:[sample.quantity doubleValueForUnit:[[HKUnit countUnit] unitDividedByUnit:[HKUnit minuteUnit]]]]]; 
      [dataDates addObject:sample.startDate]; 
     } 
     monthForCalculation = monthsBack; 
     chronologicalDataValues = [[NSMutableArray alloc] init]; 
     chronologicalDataDates = [[NSMutableArray alloc] init]; 
     chronologicalDataValues = [[[dataValues reverseObjectEnumerator] allObjects] mutableCopy]; 
     chronologicalDataDates = [[[dataDates reverseObjectEnumerator] allObjects] mutableCopy];   
     //dispatch_async(dispatch_get_main_queue(), ^{ 
      if(dataDates.count == 0){ 
       ViewController *commonClass =[[ViewController alloc] init]; 
       [commonClass receiveCalculationData:[[NSMutableArray alloc] init] array:[[NSMutableArray alloc] init] daysToDisplay:[[NSMutableArray alloc] init] chosenMonth:monthForCalculation]; 
      } 
      else{ 
       NSLog(@"%@", [dataDates objectAtIndex:dataDates.count-1]); 
       NSLog(@"%@", [dataDates objectAtIndex:0]); 
       [self calculateDayStringsFromData]; 
      } 

      completionBlock(); 
     //}); 
    }]; 
    NSLog(@"HKSampleQuery Requested For Heart Rate Data"); 
    [healthStoreFive executeQuery:query]; 

//}}

ответ

2

Вы можете использовать dispatch_group, чтобы запланировать блокировку после завершения всех ваших задач.

Вы просто должны изменить свой метод calculateData: принять dispatch_group_t аргумент (вы всегда можете добавить блок завершения, а также в случае необходимости):

- (void)calculateData:(NSInteger)monthsBack group:(dispatch_group_t)group { 

    dispatch_group_enter(group); // increment group task count 

    //Some other stuff 

    HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:heartRate predicate:monthPeriodPredicate limit:200000 sortDescriptors:@[timeSortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) { 

     //Finish Calculations, call other functions (ie. [self doThis];) and then return 

     dispatch_group_leave(group); // decrement task count 
    }]; 

    [healthStoreFive executeQuery:query]; 

} 

Тогда вы можете просто назвать его так:

HeartRateCalculator *commonClassTwo =[[HeartRateCalculator alloc] init]; 

dispatch_group_t group = dispatch_group_create(); 

[commonClassTwo calculateData:0 group:group]; 
[commonClassTwo calculateData:-1 group:group]; 
[commonClassTwo calculateData:-2 group:group]; 

dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // called when all tasks are finished. 
    // update UI 
}); 

Это тот же принцип, что Warif was going for, но dispatch_groups являются гораздо более элегантно, чем используя собственную переменную для отслеживания количества задач execu тин.


Хотя я не уверен, что вы имеете в виду, когда говорите, что хотите, чтобы задачи выполнялись асинхронно. От Apple docs on HKSampleQuery:

Запросы выполняются в анонимной очереди фона.

Поэтому ваши задачи уже асинхронный.

+0

Итак, извините, но я все еще не очень счастлив. Я обнаружил, что все HKSampleQueries возвращаются по порядку, но переменная, которую я генерирую непосредственно перед запросом (в частности, дата начала для предиката), изменяется каждый раз до возвращения запроса. Тогда я не могу использовать его в последующих вычислениях. Есть идеи? –

+0

@SimonEdwardes, где вы определяете 'monthPeriodPredicate'? Пожалуйста, добавьте полный код в OP – Hamish

+0

Сделали, это помогает? –

2

Использование block

[commonClassTwo calculateData:0 withCompletionBlock:^{ 
    [commonClassTwo calculateData:-1 withCompletionBlock:^{ 
     [commonClassTwo calculateData:-2 withCompletionBlock:^{ 
      NSLog(@"All 3 calculation done"); 
     }]; 
    }]; 
}]; 

- (void)calculateData:(NSInteger)monthsBack withCompletionBlock:(void(^)())completionBlock { 
    //Some other stuff 
    HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:heartRate predicate:monthPeriodPredicate limit:200000 sortDescriptors:@[timeSortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) { 
     //Finish Calculations, call other functions (ie. [self doThis];) and then return 
     completionBlock(); 
    } 
    [healthStoreFive executeQuery:query]; 
} 

Update

Для асинхронного вызова установите счетчик integer. Исходным значением будет количество асинхронных вызовов, которые вы хотите иметь. (здесь 3)
В завершении блокировки каждого вызова уменьшите счетчик и проверьте, завершены ли все вызовы или нет.
если завершено обновление UI.

[commonClassTwo calculateData:0 withCompletionBlock:^{ 
    // counter--; 
    // [self updateUI]; 
}]; 

[commonClassTwo calculateData:-1 withCompletionBlock:^{ 
    // counter--; 
    // [self updateUI]; 
}]; 

[commonClassTwo calculateData:-2 withCompletionBlock:^{ 
    // counter--; 
    // [self updateUI]; 
}]; 


- (void)updateUI { 
    // if counter == 0 { 
    //  dispatch_async(dispatch_get_main_queue(), ^{ 
    //   update UI 
    //  }); 
    // } 
} 
+1

Отлично, спасибо. Отлично работает, единственное, что он немного медленный. Как я буду запускать их одновременно? Потому что, если я прав, то в данный момент он делает это и заканчивает, прежде чем делать следующее? cheers –

+0

цитата из вопроса: 'Я хотел бы иметь возможность отключать их синхронно, и когда все они вернутся, я могу обновить интерфейс. Ваш предыдущий код делал это одновременно. просьба уточнить. –

+0

@SimonEdwardes ** вне темы: ** вы никогда не принимали ни одного из ваших вопросов. Это может помочь http://stackoverflow.com/tour. –

0

Вы можете сделать раздельные классы вместо и запустить их все одновременно.

HeartRateCalculator *commonClassOne =[[HeartRateCalculator alloc] init]; 
HeartRateCalculator *commonClassTwo =[[HeartRateCalculator alloc] init]; 
HeartRateCalculator *commonClassThree =[[HeartRateCalculator alloc] init]; 
[commonClassOne calculateData:0]; 
[commonClassTwo calculateData:-1]; 
[commonClassThree calculateData:-2]; 

Таким образом, вам не нужно дублировать код, но они не будут перезаписывать значения в одном классе.

Смежные вопросы