表reloadData可能导致崩溃

时间:2015-06-10 13:28:00

标签: objective-c uitableview

非常偶尔(每周一次)Crashlytics报告崩溃,例如

Fatal Exception: NSRangeException
-[__NSArrayM objectAtIndex:]: index 7 beyond bounds [0 .. 1]

[MusicList tableView:heightForRowAtIndexPath:]

我想知道这些是否是由于在表格完成渲染之前重新加载数据而发生的,所以当它请求一段数据时它已被删除但尚未重新插入?

如果是这种情况,最好是从reloadData移除viewWillAppear来电并将其放入viewDidAppear?但随后它将显示可能的旧数据,然后使用可能对最终用户产生不利影响的新内容进行刷新。

这是我目前为控制器编写的代码:

@implementation MusicList

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setTitles:[[NSMutableArray alloc] init]];
    [self setData:  [[NSMutableArray alloc] init]];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    dispatch_queue_t loadMusic = dispatch_queue_create("loadMusic", NULL);
    dispatch_async(loadMusic, ^{
        NSMutableDictionary * data = [Music getMusicList];

        [self setTitles:[data objectForKey:@"titles"]];
        [self setData:  [data objectForKey:@"tracks"]];

        dispatch_async(dispatch_get_main_queue(), ^{
            [tableMusicList reloadData];
        });
    });
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [_titles count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [_data[section] count];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return [_titles[indexPath.section] isEqualToString:@"NowPlaying"]
        ? 100.0
        : 44.0;
}

更新:从MusicList返回的数据:

- (NSMutableDictionary *)getMusicList {
    Db                  * db   = [[Db alloc] init];
    NSMutableDictionary * data = [[NSMutableDictionary alloc] init];

    [data setObject:[[NSMutableArray alloc] init] forKey:@"titles"];
    [data setObject:[[NSMutableArray alloc] init] forKey:@"tracks"];

    if ([db prepare:@"SELECT `date`, `name` FROM `music` ORDER BY `date` DESC LIMIT 25"]) {
        NSString * date;
        NSString * name;

        while ([db stepThrough]) {
            date = [db get:0];
            name = [db get:1];

            if (date && name) {
                [[data objectForKey:@"titles"] addObject:date];
                [[data objectForKey:@"tracks"] addObject:name];
            }
        }
    }

    return data;
}

2 个答案:

答案 0 :(得分:0)

问题出在您的UITableView代理中,即使苹果没有关于此代理的调用顺序的文档,您也可以随时检查并查看每个代表中NSLog();的内容。

你的代码中的

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 
    return [_titles[indexPath.section] isEqualToString:@"NowPlaying"] ? 100.0: 44.0;
}

依赖于另一个代表

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [_titles count];
}

这个代表会告诉indexPath有多少部分,如果在heightForRowAtIndexPath之前先调用numberOfSectionsInTableView怎么办?

另一个,正在重新加载-viewWillAppear内的UITableView,我也不知道背后的原因,但我之前遇到过这个问题,我做的是仔细检查UITableViewDelegates中的dataSource,如:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

    if (!_titles) // just to double check if _title is allocated and avoid `[NSNull length] error`
        _titles = [[NSMutableArray alloc] init];

    return [_titles count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    if (section < _data.count) // just to prevent out of bounds
         return [_data[section] count];

    return 0; // zero number of row in section if out of bounds
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    if (indexPath.section < _titles.count) // just to prevent out of bounds
        return [_titles[indexPath.section] isEqualToString:@"NowPlaying"] ? 100.0 : 44.0;

    return 0;  // zero height of row in at indexPath if out of bounds
}

这是我以动态的方式使用_titles[indexPath.section]variable[index]学到的东西,

总是检查BOUNDS以避免OUT(BOUNDS)..快乐的编码,干杯!

答案 1 :(得分:0)

我在10日更新了代码,将数据加载到viewDidAppear函数中,所有内容现在都按预期工作了 - 而我们每隔几天就收到一次崩溃报告,我们还没有收到一次崩溃12天,所以一切都很好。

我最终可以通过添加一些位置很好的NSLog来重现错误,并且可以在所有cellForRowAtIndexPath&#39;之前看到数据偶尔完成加载。 s已经运行了,所以错误肯定是而不是@droppy在MusicList传回的数据被传回的内容。

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setTitles:[[NSMutableArray alloc] init]];
    [self setData:  [[NSMutableArray alloc] init]];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    dispatch_queue_t loadMusic = dispatch_queue_create("loadMusic", NULL);
    dispatch_async(loadMusic, ^{
        NSMutableDictionary * data = [Music getMusicList];

        [self setTitles:[data objectForKey:@"titles"]];
        [self setData:  [data objectForKey:@"tracks"]];

        dispatch_async(dispatch_get_main_queue(), ^{
            [tableMusicList reloadData];
        });
    });
}