1

I am working with a ViewController, that delegates & datasources 2 different tableviews. according to what the user wishes to see (toggling happens with 'touchesBegan' for certain areas in the view.

The viewcontroller is one of 3 subcontrollers of a tabbed application.

The alternation between the tableviews works just fin. i get the right data, layout, etc and i can change between them as often as i want to.

the first tableview does not contain difficult data so it is loaded in a few milliseconds.

the second tableview2 contains data that takes about 2-3 seconds to load (depending on the amount of entities in coredata). while this data loads & the tableview2 redraws i show a MBProgressHUD. this works as well.

Problem: if i interact with the tabbarcontroller while table2 is loading & the hud is spinning, the app 'freezes' the hud runs infinitly long and any userinteractino is disabled. as well the clicked tab would not open.

CODE: touchesBegan FUnction

    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{


    if (isLoadingAllMonth) {
        return;
    }
    [[MBProgressHUD HUDForView:self.view]removeFromSuperview];


    UITouch *touch = [[event allTouches]anyObject];
    int viewTag = touch.view.tag;                           // 1 for thisMonth, 2 for allMonth


    if (viewTag == 1) {
        [allMonthButtonView setAlpha:.8];
        [thisMonthButtonView setAlpha:1];
        if (allMonthIsActive == NO) {
            return;
        }
        else{
            [self reloadThisMonth];
            [allMonthTable removeFromSuperview];
            [self.view addSubview:thisMonthTable];

            allMonthIsActive = NO;
        }
    }

    else if(viewTag == 2){

        if (!allMonthIsActive) {
            allMonthIsActive = YES;



            if (isLoadingAllMonth) {
                return;
            }

            [self.view addSubview:HUD];
            [allMonthTable setFrame:CGRectMake(4, 64, 312, 343)];
            [allMonthTable setBackgroundColor:[self grayColor]];
            [self.view addSubview:allMonthTable];

            HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
                isLoadingAllMonth = YES;

                [self reloadAllMonth];

                dispatch_async(dispatch_get_main_queue(), ^(void) {

                    [self.allMonthTable reloadData];            // Or reload tableView
                    [HUD hide:YES];
                });

            });
        }
    }

}

CODE: reloadAllMonth

-(void)reloadAllMonth{
    UIFont *titleFont = [UIFont fontWithName:@"Cochin" size:14.0];
    UIFont *detailFont = [UIFont fontWithName:@"Cochin" size:18.0];
    [[MBProgressHUD HUDForView:self.view] setLabelFont:titleFont];
    [[MBProgressHUD HUDForView:self.view] setDetailsLabelFont:detailFont];

    [[MBProgressHUD HUDForView:self.view] setLabelText:@"Please wait"];


    [[MBProgressHUD HUDForView:self.view] setDetailsLabelText:@"Cleaning Cache"];
    if (allMonthData.count != 0) {
        for (NSMutableArray *arr in allMonthData) {
            [arr removeAllObjects];
        }

        [allMonthData removeAllObjects]; 

        for (NSMutableArray *arr in allMonthDataNumbers) {
            [arr removeAllObjects];
        }

        [allMonthDataNumbers removeAllObjects]; 

    }

    NSDate *rootDate = [NSDate dateWithTimeIntervalSinceReferenceDate:(10*365*24*60*60)];
    int rootMonth = [[dataHandler getMonthNumber:rootDate] intValue];

    NSMutableArray *allExp = [[NSMutableArray alloc]init];
    NSNumber *currentMonth = [dataHandler getMonthNumber:[NSDate date]];
    NSMutableArray *temp = [[NSMutableArray alloc]init ];
    NSNumber *tempMonth = [NSNumber numberWithInt:(currentMonth.intValue+1)];

    [temp removeAllObjects];
    [[MBProgressHUD HUDForView:self.view] setDetailsLabelText:@"Updating Data..."];

    while (tempMonth.intValue > rootMonth) {
        tempMonth = [NSNumber numberWithInt:(tempMonth.intValue-1)];
        temp = [NSMutableArray arrayWithArray:[dataHandler fetchAllExpensesForMonth:tempMonth]] ;
        if (temp.count != 0) {
            [allExp addObject:temp];
        }

    }
    allMonthData = allExp;

    if (!allMonthDataNumbers) {
        allMonthDataNumbers = [[NSMutableArray alloc]init];

    }
    [[MBProgressHUD HUDForView:self.view] setDetailsLabelText:@"Updating Balances..."];

    for (NSArray *current in allMonthData) {
        Expense *exp = [current objectAtIndex:0];
        NSNumber *monthNumber = exp.month;
        double budget = 0;
        double spent = 0;
        double balance = 0;
        int count = 0;
        double avgDayBal = 0;

        for (Expense *exp in current) {                   // iterate this month
            if (exp.expenseType.boolValue == 0) {                 // all day type expensees
                spent = spent+exp.value.doubleValue;
                count ++;
            }
            else if (exp.expenseType.boolValue == 1) {
                budget = budget+exp.value.doubleValue;
            }
        }
        balance = budget+spent;
        avgDayBal = balance/[dataHandler numberOfDaysInMonthForMonth:monthNumber];

        NSMutableArray *temp = [[NSMutableArray alloc]init];
        [temp addObject:monthNumber];
        [temp addObject:[NSNumber numberWithDouble:budget ]];
        [temp addObject:[NSNumber numberWithDouble:spent ]];
        [temp addObject:[NSNumber numberWithDouble:balance ]];
        [temp addObject:[NSNumber numberWithInt:count ]];
        [temp addObject:[NSNumber numberWithDouble:avgDayBal ]];

        [allMonthDataNumbers addObject:temp];
    }
    NSNumber *day = [dataHandler getDayNumber:[NSDate date] ];
    [[allMonthDataNumbers lastObject] addObject:day];

    NSLog(@"We have %d month", [allMonthDataNumbers count]);

    [[MBProgressHUD HUDForView:self.view] setDetailsLabelText:@"Updating Interface..."];


    [allMonthTable reloadData];
    isLoadingAllMonth = NO;

}

CODE: reloadThisMonth

-(void)reloadThisMonth{

    [dataHandler updateData];

    if (!tableData) {
        tableData = [[NSMutableArray alloc]initWithCapacity:31];
    } 

    for (NSMutableArray *temp in tableData) {
        [temp removeAllObjects];
    }
    [tableData removeAllObjects];

    for (int j = 0; j < 31; j++) {          //fill with 31 empty mutuable arrays
        NSMutableArray *tempArray = [[NSMutableArray alloc]init];
        [tableData addObject:tempArray];
    }

    for (Expense *exp in dataHandler.allMonthExpenses) {
        if (exp.expenseType.boolValue == 0) {
            [[tableData objectAtIndex:(exp.day.intValue-1)]addObject:exp];
        }
    }
    int countDayExp = 0;
    for (NSMutableArray *arr in tableData) {
        countDayExp = countDayExp + arr.count;
    }
    if (countDayExp == 0) {
        hasDayExpenses = NO;
    }
    else{
        hasDayExpenses = YES;
    }

    [thisMonthTable reloadData];
    [thisMonthTable setBackgroundColor:[self grayColor]];

}

does anyone see where i went wrong? or what else cound be the problem? both tables show fine. if i dont interact with the app while loading the second view everything works perfectly. any ideas?

update:

apparently two threads collide when grapping the same fetchroutine at the same time - here is a screenshot of a paused debug state while the app is 'hung'. if i open another tab that needs the same fetching routine the app hangs. if i debug & pause in the hung state it shows me a line in the fetching routine. its the first time i work with threads - i would really appreciate some input on how to avoid this collision :/

threads colliding

Sebastian Flückiger
  • 5,525
  • 8
  • 33
  • 69
  • Sorry, don't have time to read through your code for a proper answer, but it doesn't look like you are using GCD. If you throw the code that loads the other table into a separate thread, then the main thread can process the UI with priority and you would probably get rid of any lag. – Anthony C Mar 25 '12 at 14:33
  • Agree about GCD. My suggestion would be to try to isolate cause. Maybe build a little project with just this complex operation and a single table. Didn't detail read either, but it's likely that the long operation is hanging than the OS. – danh Mar 25 '12 at 14:47
  • Another hint: get it into the hung state in debug and pause it. You might find the bug right away. – danh Mar 25 '12 at 14:48
  • well the long operation works fine if i dont disturb it. only clcks on the tabbar habe that effect, if i click elsewhere no problem appears... ill try yout debug hint as soon as k get home hanks – Sebastian Flückiger Mar 25 '12 at 16:04
  • to note: this is the first time im using threads and just for that one method because mbprogresshud does it automatically... – Sebastian Flückiger Mar 25 '12 at 16:22
  • interesting - if i pause during the 'hang' the debugger shows a line of my dataHandling object where it fetches. this makes sense somehow. the other tab that i press to get the app to freeze displays a tableview as well, AND it uses the very same function of the datahandler to fill its cells. i think the two threads are colliding by trying to make the same fetch.. since i have never worked with threads before i stand before a riddle - how can i make sure that the threads dont 'touch' each other there? – Sebastian Flückiger Mar 25 '12 at 16:57

1 Answers1

0

I had a similar problem with MBProgressHUD while running tasks in another thread. The way I solved it was to use a *HUD property everywhere instead of a local variable. It looks like your using a local variable in some places and a instance variable in others.

@property (strong, nonatomic) MBProgressHUD *HUD;

Then use it like:

HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
HUD.delegate = self;

Execute your code in another thread, Then call the main thread using GCD to hide it and remove the HUD:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {

//Your Code to execute in the background

      dispatch_async(dispatch_get_main_queue(), ^(void) {
             //Code here to update stuff on main thread
             //Example: hide hud or change hud label
                    HUD.labelText = @"Foo Done, Bar started"; //change label
                    [HUD hide:YES];                          // or hide HUD
                    [self.tableView reloadData];            // Or reload tableView
                });

});

And the MBProgressHUD Delegate:

- (void)hudWasHidden:(MBProgressHUD *)hud {
    // Remove HUD from screen when the HUD was hidded
    [HUD removeFromSuperview];
    HUD = nil;
}

The method:

[HUD showWhileExecuting:@selector(reloadAllMonth) onTarget:self withObject:nil animated:YES];

is the same as where I ran into the most trouble. It looks like the HUD doesn't know that your view was changed and thinks the selector is not finished and keeps running blocking the main thread. So I got rid of that method and used the instance Variable *HUD and GCD, retrieving the main thread whenever I needed to update the HUD's label or hide/remove it as in the above code.

Hubert Kunnemeyer
  • 2,261
  • 1
  • 15
  • 14