iOS中使用GCD进行UICollectionView的竞争条件

时间:2015-05-29 18:46:01

标签: ios objective-c grand-central-dispatch

我希望有人可以帮我解决这个问题并尝试找出优化它的最佳方法,这样我就不会有这么多竞争条件。

我知道的一些事情正在发生:

  • 有时集合视图甚至不显示
  • 只有一些项目出现在馆藏视图中,即8个项目中的3个
  • 刷新图形函数随机调用1-3次

我只是认为这个代码可以提高效率。如果有人有任何建议,我将非常感激。

我将在这里发布大部分代码,我已经删除了大量处理来保存行,所以它不是很难读,但我想我已经离开了,所以你可以看到GCD在哪里被利用,如果我做错了。如果你想看到整件事,我还会在一个要点中链接完整的代码。

Gist

#import "DashboardCollectionViewController.h"
#import "DashboardLayout.h"
#import "HeaderViewCell.h"
#import "DashboardCell.h"
#import "DashboardDetailViewController.h"
#import "PreferencesManager.h"
#import "UIColor+HexColors.h"
#import "PNChart.h"

@interface DashboardCollectionViewController () <UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate>

@property (nonatomic, strong) DashboardLayout *listLayout;
@property (nonatomic, strong) DashboardLayout *gridLayout;
@property (strong, nonatomic) HKHealthStore *healthStore;
@property (strong, nonatomic) NSDictionary *dataTypes;
@property (strong, nonatomic) HeaderViewCell *headerCell;

@end

@implementation DashboardCollectionViewController
{

    NSString *layoutType;
    NSString *unitPreference;
    NSMutableDictionary *itemData;
    UIRefreshControl *refreshControl;
    NSArray *sortedDashboardItems;
    __block NSMutableArray *graphData;

}

@synthesize dashboardItems, allDashboardItems, myCollectionView, dataTypes, headerCell;

- (void)viewDidLoad {

    [super viewDidLoad];
    // Do any additional setup after loading the view.

    [self setupFlowLayoutA];
    [self setupFlowLayoutB];

    layoutType = @"listView";
    dataTypes = [NSDictionary dictionaryWithObjectsAndKeys:
                 @1, HKQuantityTypeIdentifierStepCount,
                 @2, HKQuantityTypeIdentifierFlightsClimbed,
                 @3, HKQuantityTypeIdentifierDistanceWalkingRunning,
                 @4, HKQuantityTypeIdentifierActiveEnergyBurned,
                 @5, HKQuantityTypeIdentifierBodyMass,
                 @6, HKQuantityTypeIdentifierDistanceCycling,
                 @7, HKQuantityTypeIdentifierHeartRate,
                 @8, HKQuantityTypeIdentifierBodyMassIndex, nil];

    UINib *headerNib = [UINib nibWithNibName:@"HeaderView" bundle:nil];
    UINib *cellNib = [UINib nibWithNibName:@"DashboardCell" bundle:nil];
    DashboardLayout *dashboardLayout = [self listLayout];

    [myCollectionView registerNib:headerNib forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"HeaderCell"];
    [myCollectionView registerNib:cellNib forCellWithReuseIdentifier:@"Cell"];
    [myCollectionView setCollectionViewLayout:dashboardLayout animated:YES];

    //check if health kit is available on this device
    if ([HKHealthStore isHealthDataAvailable]) {

        if (!self.healthStore) {
            self.healthStore = [HKHealthStore new];
        }
    }

    refreshControl = [UIRefreshControl new];
    [refreshControl addTarget:self action:@selector(refreshAllData) forControlEvents:UIControlEventValueChanged];
    [myCollectionView addSubview:refreshControl];

}

- (void)viewWillAppear:(BOOL)animated {

    [super viewWillAppear:animated];

    //set data types we want to read and write
    NSSet *dataTypesToWrite = [self dataTypesToWrite];
    NSSet *datatTypesToRead = [self dataTypesToRead];

    allDashboardItems = [self loadDashboardItems];
    unitPreference = [self loadUnitPreferences];
    dashboardItems = [NSMutableArray new];
    itemData = [NSMutableDictionary new];

    for (NSDictionary *item in allDashboardItems) {

        NSString *enabled = item[@"enabled"];

        if ([enabled isEqualToString:@"1"]) {
            [dashboardItems addObject:item];
        }

    }

    NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"order" ascending:YES];
    sortedDashboardItems = [dashboardItems sortedArrayUsingDescriptors:@[descriptor]];

    graphData = [[NSMutableArray alloc] initWithCapacity:[sortedDashboardItems count]];

    for (int i=0; i < [sortedDashboardItems count]; i++) {

        [graphData addObject:[NSNull null]];

    }

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
#if DEBUG
    NSLog(@"%@", [defaults objectForKey:@"unitPreference"]);
#endif

    //request authorization
    [self.healthStore requestAuthorizationToShareTypes:dataTypesToWrite readTypes:datatTypesToRead completion:^(BOOL success, NSError *error) {
        if (!success) {
            //user did not authorize healthkit
            NSLog(@"Health Kit was not given the correct permissions");
            return;
        } else {

            [self refreshData];
            [self refreshGraphs];
           // [myCollectionView reloadData];

        }
    }];

    [self refreshData];
    [self refreshGraphs];
//    [myCollectionView reloadData];

}

- (void)refreshAllData {

    [self refreshData];
    [self refreshGraphs];

}

- (void)refreshData {

    __block NSString *labelString = @"";
    __block NSString *unitString = @"";

    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 (NSDictionary *item in sortedDashboardItems) {

        NSString *type = item[@"type"];

        HKSampleType *sampleType = [HKSampleType quantityTypeForIdentifier:type];

        HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:predicate limit:HKObjectQueryNoLimit sortDescriptors:nil resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {

            if (!results) {

                NSLog(@"No results were returned form query");

            } else if (error) {

                NSLog(@"Error: %@ %@", error, [error userInfo]);

            } else {

                dispatch_async(dispatch_get_main_queue(), ^{

                    //processing

                    int order = [item[@"order"] intValue];
                    NSNumber *orderNumber = [NSNumber numberWithInt:order];

                    UIFont *arialLarge = [UIFont fontWithName:@"AvenirNext-Bold" size:15.0];
                    UIFont *arialSmall = [UIFont fontWithName:@"AvenirNext-Bold" size:8.0];
                    NSDictionary *arialLargeDict = @{NSFontAttributeName : arialLarge};
                    NSDictionary *arialSmallDict = @{NSFontAttributeName : arialSmall};

                    NSMutableAttributedString *largeString = [[NSMutableAttributedString alloc] initWithString:labelString attributes:arialLargeDict];
                    NSMutableAttributedString *smallString = [[NSMutableAttributedString alloc] initWithString:unitString attributes:arialSmallDict];

                    [largeString appendAttributedString:smallString];

                    [itemData setObject:largeString forKey:orderNumber];

                    [myCollectionView reloadData];
                    [refreshControl endRefreshing];

                });

            }

        }];

        [self.healthStore executeQuery:query];

    }

}

- (void)refreshGraphs {

#if DEBUG
    NSLog(@"graphs");
#endif
    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSDateComponents *interval = [NSDateComponents new];
    interval.day = 1;

    NSDate *anchorDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:-6 toDate:[calendar startOfDayForDate:[NSDate date]] options:0];

    dispatch_queue_t queue = dispatch_queue_create([@"graph.queue" UTF8String], DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();

    for (NSDictionary *item in sortedDashboardItems) {

        NSMutableArray *arrayOfValues = [NSMutableArray new];

        NSString *type = item[@"type"];

        NSUInteger index = [sortedDashboardItems indexOfObject:item];

        HKQuantityType *quantityType = [HKObjectType quantityTypeForIdentifier:type];

        NSDate *endDate = [NSDate date];

        NSDate *startDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:-6 toDate:[calendar startOfDayForDate:endDate] options:0];

        dispatch_block_t block = ^{

            dispatch_semaphore_t lock = dispatch_semaphore_create(0);

            if ([type isEqualToString:HKQuantityTypeIdentifierBodyMass]) {

                HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType quantitySamplePredicate:nil options:HKStatisticsOptionDiscreteAverage anchorDate:anchorDate intervalComponents:interval];

                query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) {

                    //processing

                };

                [self.healthStore executeQuery:query];

                dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);

            } else if ([type isEqualToString:HKQuantityTypeIdentifierBodyMassIndex]) {

                HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType quantitySamplePredicate:nil options:HKStatisticsOptionDiscreteAverage anchorDate:anchorDate intervalComponents:interval];

                query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) {

                    //processing

                };

                [self.healthStore executeQuery:query];

                dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);

            } else if ([type isEqualToString:HKQuantityTypeIdentifierHeartRate]) {

                HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType quantitySamplePredicate:nil options:HKStatisticsOptionDiscreteAverage anchorDate:anchorDate intervalComponents:interval];

                query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) {

                    //processing

                };

                [self.healthStore executeQuery:query];

                dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);

            } else {

                HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType quantitySamplePredicate:nil options:HKStatisticsOptionCumulativeSum anchorDate:anchorDate intervalComponents:interval];

                query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) {

                    if (error) {

                        NSLog(@"Error: %@ %@", error, [error userInfo]);

                    } else {

                        [results enumerateStatisticsFromDate:startDate toDate:endDate withBlock:^(HKStatistics *result, BOOL *stop) {

                            HKQuantity *quantity = result.sumQuantity;

                            if (quantity != nil) {

                                //do some processing

                            } else {

                                [arrayOfValues addObject:@0];

                            }

                        }];

                        [self addArrayToGraphData:arrayOfValues atIndex:index];

                        dispatch_semaphore_signal(lock);

                    }

                };

                [self.healthStore executeQuery:query];

                dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);

            };

        };

        dispatch_group_async(group, queue, block);

    }

    dispatch_group_notify(group, queue, ^{

        [myCollectionView reloadData];

    });

}

- (void)addArrayToGraphData:(NSArray *)array atIndex:(NSUInteger)index {

    [graphData replaceObjectAtIndex:index withObject:array];

}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

    DashboardCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];

    if (cell != nil) {

        for (UIView *subview in cell.subviews) {

            if ([subview isKindOfClass:[PNLineChart class]]) {

                [subview removeFromSuperview];

            }

        }

        NSNumber *indexNumber = [NSNumber numberWithInteger:indexPath.row];
        NSDictionary *item = [sortedDashboardItems objectAtIndex:indexPath.row];
        NSString *imageSafeString = [item[@"title"] stringByReplacingOccurrencesOfString:@" " withString:@""];
        NSString *imageString = @"";

        if ([layoutType isEqualToString:@"listView"]) {

            imageString = [NSString stringWithFormat:@"%@-large.png", imageSafeString];
            cell.layoutType = layoutType;
            cell.titleLabel.text = item[@"title"];
            cell.dataImage.image = [UIImage imageNamed:imageString];

        } else if ([layoutType isEqualToString:@"gridView"]) {

            imageString = [NSString stringWithFormat:@"%@-small.png", imageSafeString];
            cell.layoutType = layoutType;
            cell.titleLabel.text = @"";
            cell.dataImage.image = [UIImage imageNamed:imageString];

        }

        PNLineChart *graph = nil;

        if ([layoutType isEqualToString:@"listView"]) {

            graph = [[PNLineChart alloc] initWithFrame:CGRectMake(80, 50, self.view.frame.size.width - 100, 90)];

        } else {

            graph = [[PNLineChart alloc] initWithFrame:CGRectMake(10, 0, (self.view.frame.size.width / 2) - 50, 110)];

        }

        graph.showLabel = NO;
        graph.showGenYLabels = NO;
        graph.backgroundColor = [UIColor clearColor];
        graph.pointColor = [UIColor whiteColor];
        graph.userInteractionEnabled = NO;

        [cell addSubview:graph];

        PNLineChartData *data = [PNLineChartData new];
        data.color = [UIColor colorWithHexString:@"49b1d9"];
        if (![graphData[indexPath.row] isEqual:[NSNull null]]) {
            data.itemCount = [graphData[indexPath.row] count];
        } else {
            data.itemCount = 0;
        }
        data.inflexionPointStyle = PNLineChartPointStyleCircle;
        data.getData = ^(NSUInteger index) {

            CGFloat yValue = [graphData[indexPath.row][index] floatValue];
            return [PNLineChartDataItem dataItemWithY:yValue];

        };

        graph.chartData = @[data];
        [graph strokeChart];

        NSAttributedString *dataString = [itemData objectForKey:indexNumber];

        cell.dataLabel.attributedText = dataString;

    }

    return cell;

}

1 个答案:

答案 0 :(得分:1)

似乎代码太复杂了,任何人都无法查看它并猜测实际发生了什么。如果将它分成适当的模型或服务层,这样做会很好吗,这样进行表示的代码和从healthkit获取数据的代码就会是独立的。

我清楚地看到这里的线程问题,首先是使用HKHealthStore方法,

- (void)requestAuthorizationToShareTypes:(NSSet *)typesToShare
                               readTypes:(NSSet *)typesToRead
                              completion:(void (^)(BOOL success,
                                                   NSError *error))completion 

在文档中明确指出此方法运行并且在某个任意队列上调用回调,

https://developer.apple.com/library/prerelease/ios/documentation/HealthKit/Reference/HKHealthStore_Class/index.html#//apple_ref/occ/instm/HKHealthStore/requestAuthorizationToShareTypes:readTypes:completion

  

HealthKit异步执行这些请求。如果你这样称呼   具有新数据类型的方法(用户没有的数据类型)   以前在此应用程序中授予或拒绝的权限),系统   自动显示权限表单,列出所有请求   权限。用户完成响应后,此方法调用   它在后台队列上的完成块。如果用户已经   选择授予或禁止访问指定的所有类型,   调用完成时不会提示用户。

现在,您确定要在主线程中执行所有UI调用吗?我认为您正在执行多个异步查询。因此,在这种情况下,dispatch_semaphore看起来并不复杂。使用 dispatch_group_enter dispatch_group_leave 管理并发任务会更有意义吗?我没有看到你正在主线程中重新加载collectionview。这些是我从上面的代码中看到的主要问题。如果您将服务代码提取到某些不同的对象,您可以节省大量的时间重构并计算出错误。