小心UITableView中的"数组越界Crash"

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小心UITableView中的"数组越界Crash"相关的知识,希望对你有一定的参考价值。

参考技术A

使用reloadData刷新UITableView时候,UITableViewDataSource和UITableViewDelegate的回调方法中,有些是同步发生的,有些则是异步发生的(如:cellForRowAtIndexPath是异步执行的)。

例1 :同步的回调:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
return 44;

例2 :异步的回调:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath )indexPath
//... NSString content = self.dataSource[indexPath.row];
//...

总结: 如果在异步执行cellForRowAtIndexPath时候,self.dataSource发生了变化(如删除某一个元素),极有可能发生数组越界的异常。因为reloadData返回之后,下一次loop就开始执行异步的操作了。在多线程场景下,列表界面的数据有可能经常变化,就可能会出现偶现的bug了;当列表界面数据不怎么变化的时候,几乎感知不到这种异常的存在。

目前的处理办法是 :因为dataSource类型是NSMutableArray,为NSMutableArray提供一个safeObjectAtIndex方法,这个方法放到NSMutableArray的分类中。

NSMutableArray的safeObjectAtIndex声明和实现如下

小尾巴: 我们为什么不使用runtime的特性,混写NSMutableArray的objectAtIndex

参考链接: 危险的UITableView

didSelectRowAtIndexPath 中的数组存在问题。越界

【中文标题】didSelectRowAtIndexPath 中的数组存在问题。越界【英文标题】:issue with array in didSelectRowAtIndexPath. Out of bounds 【发布时间】:2012-01-08 14:04:31 【问题描述】:

我正在尝试推送到将显示有关项目的全部信息的最终视图。

- (void)tableView:(UITableView *)atableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

Item *itemObj; //object that holds data for cell for current view
ItemShow *itemOverViewObj; //data object for the next view 

if(atableView==self.tableView)

    //ordinary tabeView
    itemObj=[self.itemsArray objectAtIndex:[indexPath row]];

else

    //filtered with search tableView
    itemObj=[self.filteredItems objectAtIndex:[indexPath row]];


//The array which will store the data for overview 
NSMutableArray *itemsOverViewArr=[[NSMutableArray alloc]initWithCapacity:1];
DBAccess *access=[[DBAccess alloc]init];

//method for fetching data from db and inserting into array
itemsOverViewArr=[access getItemsOverView:itemObj.itemID];
[access closeDataBase];
[access release];

//here is the evil.
itemOverViewObj=[itemsOverViewArr objectAtIndex:[indexPath row]];
[itemsOverViewArr release];
    ItemOverView *iov=[[ItemOverView alloc]initWithNibName:@"ItemOverView" bundle:nil];
    iov.title=self.nominal;
    [iov setItemShow:itemOverViewObj];
    [self.navigationController pushViewController:iov animated:YES];
    [iov release];

我之前检查过的内容:

在 getItemsOverView 方法中选择 100% 有效。 itemObj.itemID 获取正确的 int。

问题源于 itemOverViewObj=[itemsOverViewArr objectAtIndex:[indexPath row]];

错误:* Terminating app due to unaught exception 'NSRangeException', reason: '* -[NSMutableArray objectAtIndex:]: index 1 beyond bounds [0 .. 0]'

DBAccess 类中的getItemsOverview 方法

-(NSMutableArray*)getItemsOverView:(int)itemID

NSMutableArray *itemsArray=[[[NSMutableArray alloc]init]autorelease];
const char *sqlItems=sqlite3_mprintf("SELECT itm.itemID,itm.itemYear,itm.rarity,dateComment,itm.mintage,dc.dateCode as dateCode,iaval.availability as avalibility,iaval.quality as quality,itm.Mintmark,itm.specialRemark\
                                     from items itm\
                                     join itemAvailability iaval on iaval.itemID=itm.itemID\
                                     join dateCultures dc ON dc.dateCultureID=itm.dateCulture\
                                     WHERE itm.itemID=%i",itemID);
sqlite3_stmt *statement;
int sqlResult = sqlite3_prepare_v2(database, sqlItems, -1, &statement, NULL);
if ( sqlResult== SQLITE_OK)

    while (sqlite3_step(statement) == SQLITE_ROW)
    
        ItemShow *itemShow=[[ItemShow alloc]init];
        itemShow.itemID=sqlite3_column_int(statement, 0);
        char *itemYear=(char *)sqlite3_column_text(statement, 1);
        char *mintMark=(char *)sqlite3_column_text(statement, 2);
        char *masterMark=(char *)sqlite3_column_text(statement, 3);
        itemShow.rarity=sqlite3_column_int(statement, 4);
        itemShow.availability=sqlite3_column_int(statement, 5);
        itemShow.quality=sqlite3_column_int(statement, 6);
        char *mintage=(char *)sqlite3_column_text(statement, 7);

        itemShow.itemYear=(itemYear)?[NSString stringWithUTF8String:itemYear]:@"";
        itemShow.mintMark=(mintMark)?[NSString stringWithUTF8String:mintMark]:@"";
        itemShow.masterMark=(masterMark)?[NSString stringWithUTF8String:masterMark]:@"";
        itemShow.mintage=(mintage)?[NSString stringWithUTF8String:mintage]:@"Unknown";



        [itemsArray addObject:itemShow];
        [itemShow release];

    
    sqlite3_finalize(statement);

else

    [self dbConnectionError];


return itemsArray;

提前谢谢你

【问题讨论】:

【参考方案1】:

异常消息说明了一切:您正在尝试访问 itemsOverViewArr 中的第二项(索引 1),但该数组仅包含一项(索引 0)。

为什么会这样,因为您没有向我们展示相关代码,所以只有您自己才能知道。但它确实看起来像 [access getItemsOverView:itemObj.itemID] 返回一个包含 1 个元素的数组。

此外,您的代码中至少存在一次内存泄漏。 NSMutableArray *itemsOverViewArr=[[NSMutableArray alloc]initWithCapacity:1]; 创建的数组在itemsOverViewArr=[access getItemsOverView:itemObj.itemID]; 行之后不再可达,因此无法释放。您稍后释放itemsOverViewArr 的事实可能也是内存管理错误(在这种情况下为过度释放),因为getItemsOverView: 应该返回一个自动释放的对象。 (顺便说一句,您不应将此类方法命名为 get...,因为此命名约定在 Cocoa 中暗示该方法通过指向方法参数之一的指针返回其结果。)

【讨论】:

itemsOverViewArr=[access getItemsOverView:itemObj.itemID]; 后不为空,我写了 NSLog(@"%d",[itemsOverViewArr count]);检查它是否真的被填满。我得到 1。所以它被填满了。 @Nathan 1 表示一个元素,需要使用 objectAtIndex:0 来解决,因为索引数组是从零开始的。但是,您的原始报价表明该数组确实为空-因此您的报价不正确或最新的 NSLog 结果。 那么这个实现呢?:NSMutableArray * itemsOverViewArr = [[NSMutableArray alloc] initWithArray: [access getItemsOverView:itemObj.itemID]]; 好的。同时这个问题已经结束,因为问题的原因被确定了。另一个问题将被打开。【参考方案2】:

做什么:

itemsOverViewArr=[access getItemsOverView:itemObj.itemID];

做吗?

如果它返回一个数组,像这样声明你的 NSMutableArray:

NSMutableArray * itemsOverViewArr = [[NSMutableArray alloc] initWithArray: [access getItemsOverView:itemObj.itemID]];

然后还要确保在引用它时,确保该可变数组中至少有一项。

换句话说:

if(itemsOverViewArr && ([itemsOverViewArr count] > 1)) 

    itemOverViewObj=[itemsOverViewArr objectAtIndex:[indexPath row]];
    ...
    ...

【讨论】:

之前已经在我的代码中声明过:NSMutableArray *itemsOverViewArr=[[NSMutableArray alloc]initWithCapacity:1]; true... 你可以用我刚刚在答案中给你的内容替换你上面的声明,或者你也可以做[itemsOverViewArr setArray:[access getItemsOverView:itemObj.itemID]];(为你链接的文档)。 由于未捕获的异常 'NSInvalidArgumentException' 导致应用程序终止,原因:'+[Items count]: unrecognized selector sent to class 0x15f6c' 并且 itemsOverViewArr 不为空。它充满了数据。 gah... 是我的错字,我已经修复了上面的代码(从 itemOverViewObjitemsOverViewArr)。【参考方案3】:

看起来您将委托用于多个 tableView

if(atableView==self.tableView)

确保您在此处访问正确的表格视图:

itemOverViewObj=[itemsOverViewArr objectAtIndex:[indexPath row]];

否则您将在数组中获得错误的索引,该数组可能在索引 0 处只有一项,这会使索引 1 超出范围。

【讨论】:

if(atableView==self.tableView) //普通的tabeView itemObj=[self.itemsArray objectAtIndex:[indexPath row]]; else //用搜索表过滤 itemObj=[self.filteredItems objectAtIndex:[indexPath row]]; 第一个是普通表。使用 UISearch 排序后的另一个此表。两者都可以使用。 在崩溃前执行 NSLog 以查看 itemsOverViewArr 中的内容以查看其中的内容。而 indexPath 是什么,这就是被调用的。我使用 [indexPath 描述]。 我对 sqlite 真的一无所知,但作为一个建议,您可以在 itemsArray 返回之前检查它内部的内容。我会添加一些 NSLogs 来查看它在哪里以及是否有任何对象以及在哪里没有。很抱歉,我无法为您提供更多帮助。 -(NSMutableArray*)getItemsOverView:(int)itemID 在线。此方法从 db 中获取数据。

以上是关于小心UITableView中的"数组越界Crash"的主要内容,如果未能解决你的问题,请参考以下文章

iOS如何彻底避免数组越界

关于UITableview刷新笔记

android开发,数组越界,可是怎么都找不到问题,求救

NSMutableArray 仅在填充另一个数组时越界

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 数组越界

NSMutableRLEArray objectAtIndex:effectiveRange:: 填充 UITableView 时越界