dynamic UIPickerView race condition?

337 views Asked by At

I have a two level UIPickerView which means if user selects the first component, the second component will update its data.

The data should be look like this:

self.type = @[@"fruit", @"airlines"];
self.data = @[@[@"Apple", @"Orange"], @[@"Delta", @"United", @"American"]];

datasource and delegate method:

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    return 2;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
    if(component == 0){
        return self.data.count;
    }else if(component == 1){
        NSInteger row1 = [pickerView selectedRowInComponent:0];
        return [self.data[row1] count];
    }
} 


- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
    if(component == 0){
        [pickerView reloadComponent:1];
    }
    NSInteger row1 = [pickerView selectedRowInComponent:0];
    NSInteger row2 = [pickerView selectedRowInComponent:1];

    // here may crash, some time
    NSLog(@"data: %@", self.data[row1][row2]);
}


- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
    if(component == 0){
        return self.type[row];
    }else if(component == 1){
        NSInteger row1 = [pickerView selectedRowInComponent:0];
        // here may also crash
        return self.data[row1][row];
    }
}

There are several places which may cause crashes(NSRangeException) in the above code as commented. However, they rarely happened. I haven't gotten this crash yet, but my users had according to Crashlytics report.

I add some codes to validate row1 and row2 before accessing self.data[row1][row2] and send to my server if fail. I found that the value of [pickerView selectedRowInComponent:0] may be incorrect some time. For example, when user changes the first component from "fruit" to "airlines" and select some item in second component, the value of selectedRowInComponent:0 may still be 0(index of "fruit").

I guess this is caused by race condition but how can I solve this?

1

There are 1 answers

3
Jef On

It will crash if a component represents an array with three members (airlines) AND the selected row in that component is the last member and then you flip the component over to represent the two member array (fruit). The pickerview has retained this variable (selectedrowincomponent:x) and now wants to represent member #2 on a two member array (has members #0-1 only) -> NSRangeException. You need to alter the selectedRow in that component before you flip it to a different array with reload ..

I personally don't like using one picker to do two things, it's messy IMHO just write a different class to be delegate/dataSource for each, one can be a subclass of the other, then lazy load em as needed