使用不同的变量异步多次运行某些代码

Posted

技术标签:

【中文标题】使用不同的变量异步多次运行某些代码【英文标题】:Running Some Code Multiple Times Asynchronously With Different Variables 【发布时间】:2016-02-16 11:28:28 【问题描述】:

所以,我正试图让我的应用程序读取 HealthKit 数据。我有一个从主应用程序视图控制器调用的函数,它会在另一个类中查询该月的所有健康数据。然后在数据数组从计算类中的单独函数返回到视图控制器中的单独函数之前进行一些计算。

由于数据量大,每个查询大约需要 2 秒。我希望能够异步关闭它们,当它们全部返回时,我可以更新 UI。

问题是,我每个月都调用该函数,该函数启动HKSampleQueries,但它们没有按顺序返回,而且它们返回所需的时间各不相同。这意味着我最终会在一组数据计算中途更改变量,因为下一组刚刚开始。

我只知道解决这个问题的两种方法:

在调用每个计算之前设置一个延迟,如下所示:

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

但这会浪费应用时间

或者我可以多次复制代码并为每个月调用不同的类。但这似乎是愚蠢和低效的。

所以问题是。我如何有效地共享每次使用不同变量运行多次的代码。 干杯

函数示例:

在视图控制器中:

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

在心率计算器中

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

//

【问题讨论】:

【参考方案1】:

您可以使用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 比使用自己的变量来跟踪正在执行的任务数要优雅得多


虽然我不确定您说希望异步执行任务是什么意思。来自Apple docs on HKSampleQuery

查询在匿名后台队列上运行。

因此,您的任务已经异步了。

【讨论】:

所以,很抱歉报告,但我在这里仍然运气不佳。我发现所有 HKSampleQueries 按顺序返回,但是我在查询之前生成的变量(特别是谓词的开始日期)每次在查询返回之前都会更改。然后我无法在后续计算中使用它。有什么想法吗? @SimonEdwardes 你在哪里定义monthPeriodPredicate?请将完整代码添加到 OP 我现在尝试添加完整的代码,抱歉我现在才评论它 @SimonEdwardes startOfMonth 定义在哪里?看起来它可能是一个实例变量,因此每次查询时都会更改它。 干杯,伙计们,终于到了。 startOfMonth 变量在整个班级的顶部声明,我更改了它,然后将它交给了奇数函数,一切都很好。爱dispatch_group顺便说一句!【参考方案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];


更新

对于异步调用,有一个计数器 integer。初始值将是您想要的异步调用数。 (此处为 3)在每次调用的 completionBlock 中,递减计数器并检查是否所有调用都已完成。如果已完成,请更新 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
    //     );
    // 

【讨论】:

太好了,谢谢。效果很好,唯一就是有点慢。我将如何同时运行这些?因为如果我是对的,那么现在它正在做一个并在做下一个之前完成?干杯 引用问题:I would like to be able to set them off synchronously and when they have all returned, I can update the UI. 您之前的代码是同时执行的。请澄清。 @SimonEdwardes 题外话:你从来没有接受过你的任何问题。这可能会帮助***.com/tour。 抱歉,我会编辑问题,我现在只想异步,因为我希望并行运行它们会提高速度,干杯 另外值得注意的是,当您更新 UI 时,您应该首先调度到主队列。 HKSampleQuery 完成块在后台队列中调用 - UI 更新应始终在主队列中进行。【参考方案3】:

您可以改为创建单独的类并同时运行它们。

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

这样您不必复制代码,但它们不会覆盖同一类中的值。

【讨论】:

以上是关于使用不同的变量异步多次运行某些代码的主要内容,如果未能解决你的问题,请参考以下文章

异步代码是不是在 UI 线程或新/不同线程中运行以不阻塞 UI?

如何使用不同的输入多次运行蜘蛛

Ansible使用不同的变量文件多次运行相同的角色

在具有不同输入的 1x exe 中同时多次运行 python 脚本

允许某些角色在Ansible playbook中多次运行

Z3在多次运行时生成不同的型号