iOS·CityPickerView省市区选择器出现奔溃纪实:三栏联动的时数据源数组越界(Xcode奔溃调试技巧)

iOS开发过程中,有时候一些第三方省市区位置选择器PickerView出现诡异bug:在快速同时分别滑动省、市、区各栏的时候,出现奔溃。这时候,你可以打个断点,查出问题所在。笔者碰到的原因是:数组越界。

  • 这里举例的第三方省市区选择器:YLAwesomePicker于Jun22, 2017年提交的版本(该问题目前已被改开源作者于Jul 31, 2017修复)。

    iOS·CityPickerView省市区选择器出现奔溃纪实:三栏联动的时数据源数组越界(Xcode奔溃调试技巧)_第1张图片

  • 奔溃演示:


    iOS·CityPickerView省市区选择器出现奔溃纪实:三栏联动的时数据源数组越界(Xcode奔溃调试技巧)_第2张图片
  • 奔溃情景:当省一栏滑到澳门,并同时滑动第二栏第三栏时,直接崩溃。

这里记录修复这种bug的一种方案。首先看看出问题的源代码,然后指出问题所在,并给出修复方案。

1. 问题代码

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
    if(component < _componentCount){
        //remember the select data
        NSArray *array = _dataConfiguration.dataSource[@(component)];
        YLAwesomeData *currentData = array[row];
        if(_tmpSelectedData.count > component){
            _tmpSelectedData[component] = currentData;
        }else{
            [_tmpSelectedData addObject:currentData];
        }
        //ex: when select first component, the second to the last component data need refresh
        for(NSInteger nextCom = component + 1; nextCom < _componentCount; nextCom++){
            //reload data
            NSArray *nextArray = [_dataConfiguration dataInComponent:nextCom selectedComponent:component row:row];
            [pickerView reloadComponent:nextCom];
            if(nextArray.count > 0){
                NSInteger nextSelectRow = 0;
                //remember the select data
                YLAwesomeData *nextData = nextArray[nextSelectRow];
                if(_tmpSelectedData.count > nextCom){
                    _tmpSelectedData[nextCom] = nextData;
                }else{
                    [_tmpSelectedData addObject:nextData];
                }
                [pickerView selectRow:nextSelectRow inComponent:nextCom animated:NO];
            }else{
                if(_tmpSelectedData.count > nextCom){
                    //remove the last selected data
                    [_tmpSelectedData removeObjectsInRange:NSMakeRange(nextCom, _tmpSelectedData.count - nextCom)];
                }
            }
        }
    }
}


2. 问题所在

奔溃出现,在于这两句:

NSArray *array = _dataConfiguration.dataSource[@(component)];
YLAwesomeData *currentData = array[row];

如上所示,在获取array之前,并没有判断array是否为空数组?为空,array[row]能正常取值吗?所以添加一个判断即可:

if (array && array.count > 0) {
    ...
}

还有,array[row]中的row超过数组元素个数怎么办?继续追加判断:array.count>0且array.count>row。又有row>=0,故而如下即可:

if (array && array.count > row) {
    ...
}

完整修复代码

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
    if(component < _componentCount){
        //remember the select data
        NSArray *array = _dataConfiguration.dataSource[@(component)];
        if (array && array.count > row) {
            
            YLAwesomeData *currentData = array[row];
            if(_tmpSelectedData.count > component){
                _tmpSelectedData[component] = currentData;
            }else{
                [_tmpSelectedData addObject:currentData];
            }
            //ex: when select first component, the second to the last component data need refresh
            for(NSInteger nextCom = component + 1; nextCom < _componentCount; nextCom++){
                //reload data
                NSArray *nextArray = [_dataConfiguration dataInComponent:nextCom selectedComponent:component row:row];
                [pickerView reloadComponent:nextCom];
                if(nextArray.count > 0){
                    NSInteger nextSelectRow = 0;
                    
                    //remember the select data
                    YLAwesomeData *nextData = nextArray[nextSelectRow];
                    if(_tmpSelectedData.count > nextCom){
                        _tmpSelectedData[nextCom] = nextData;
                    }else{
                        [_tmpSelectedData addObject:nextData];
                    }
                    [pickerView selectRow:nextSelectRow inComponent:nextCom animated:NO];
                }else{
                    if(_tmpSelectedData.count > nextCom){
                        //remove the last selected data
                        [_tmpSelectedData removeObjectsInRange:NSMakeRange(nextCom, _tmpSelectedData.count - nextCom)];
                    }
                }
            }

        }
    }
}

3. 定位奔溃技巧

这里介绍一下为了定位奔溃原因的捕获异常断点技巧:

1. 添加异常断点

左边栏上面点击断点标签,然后点击左下角+号按钮添加断点:


iOS·CityPickerView省市区选择器出现奔溃纪实:三栏联动的时数据源数组越界(Xcode奔溃调试技巧)_第3张图片
2. 选择异常类型

选择断点捕获类型,按下图设置即可。当然你也可以只选择OC或者Swift异常。


iOS·CityPickerView省市区选择器出现奔溃纪实:三栏联动的时数据源数组越界(Xcode奔溃调试技巧)_第4张图片
第二步

4. 小结

举一反三,不仅仅是位置选择器,在通过网络获取数据并为本地模型赋值的时候,如果没有严谨在赋值取值之前判断一些对象是否为空,就经常会出现这样的崩溃。

拓展文献
  • 刨根问底:OC 中如何判断 NSArray 为空
    http://www.jianshu.com/p/ba11a53777e1

你可能感兴趣的:(iOS·CityPickerView省市区选择器出现奔溃纪实:三栏联动的时数据源数组越界(Xcode奔溃调试技巧))