小心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... 是我的错字,我已经修复了上面的代码(从 itemOverViewObj
到 itemsOverViewArr
)。【参考方案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"的主要内容,如果未能解决你的问题,请参考以下文章
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 数组越界
NSMutableRLEArray objectAtIndex:effectiveRange:: 填充 UITableView 时越界