我們習慣在開發中把數據和界面分開實現,這種方式比較好,只需要在數據和界面中同時依賴一個數據結構即可,這種做法對於解藕是一個不錯的方式。
但是有一些細節的地方可能會導致我們遇到一些很難查找的bug,比如我們之前遇到的一個問題,現在分享給大家。
先來描述一下問題:我們在UITableView中加入了一個向下拖動刷新數據的控件,控件是EGORefreshTableHeaderView。拖動後,我們就使用ASIHttpRequest刷新數據,但是在拖動幅度大一些時,ASIHttpRequest請求發出後就直接崩潰了,而且看棧也看不出崩在哪。
數據請求代碼如下:
[cpp]
- (void)startGetNewsListData
{
//newsDataArray是一個成員變量,用於取到數據後用於postNotificationName,給UITableView使用到
if (nil !=newsDataArray &&newsDataArray.count >0)
{
[newsDataArrayremoveAllObjects];
}
NSString *strURL = @"*****";//此處需要填入url的地址字符串
NSURL *url = [NSURLURLWithString:strURL];
ASIHTTPRequest *request = [ASIHTTPRequestrequestWithURL:url];
//設定委托,委托自己實現異步請求方法
[request setDelegate : self ];
// 開始異步請求
[requeststartAsynchronous ];//執行完這句後,就直接崩潰了
//[request release];
}
- ( void )requestFinished:( ASIHTTPRequest *)request
{
NSString *strRequest = [request responseString];
SBJsonParser *parser = [[SBJsonParseralloc]init];
NSDictionary *json = [parser objectWithString:strRequest error:nil];
int nCounts = [[json objectForKey:@"counts"]intValue];
NSArray *activities = [json objectForKey:@"news"];
for (int i =0; i< nCounts; i++)
{
NSDictionary *dictSummary = [activitiesobjectAtIndex:i];
NewsData *data = [[NewsDataalloc]init];
data.ID = [dictSummary objectForKey:@"id"];
......//填入NewsData的各成員變量
[newsDataArray addObject:data];
[data release];
}
......//其他邏輯
[[NSNotificationCenterdefaultCenter]postNotificationName:@"GetNewsListDataDone"object:newsDataArray];
}
猜測調試過程如下:
1)開始猜測問題是EGORefreshTableHeaderView大幅拖動導致的,於是把ASIHttpRequest請求數據注釋掉,再次大幅拖動,程序沒崩。
2)那問題一定出在ASIHttpRequest請求部分,猜測會不會是請求在子線程中做的,導致的問題,調試一下,發現請求還是在主線程中,所以也排除了這種情況。
3)一開始一直以為是ASIHttpRequest出的問題,所以精力一直放在他上面。但是後來調了1個小時,查了ASIHttpRequest的使用說明,也沒查到什麼疑點。
4)實在是沒辦法了,祭出屠龍寶刀:開始代碼分段注釋,運行看結果。問題出在
[cpp]
- (void)startGetNewsListData函數,所以從這邊開始:注釋
if (nil != newsDataArray && newsDataArray.count > 0)
{
[newsDataArray removeAllObjects];
}
這段,運行,結果正常了,http請求也能收到返回的結果了,天哪,要是早點采用這種方法,也就不用之前嘗試的1個小時了。開始分析為什麼,newsDataArray前面說了,這個數據是會通過
[cpp]
[[NSNotificationCenter defaultCenter] postNotificationName:@"GetNewsListDataDone" object:newsDataArray]; 發送,給UITableView中使用,看接收稱處的代碼:
......
[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(onGetNewsListDataSuccess:)name:@"GetNewsListDataDone"object:nil];
......
- (void)onGetNewsListDataSuccess:(NSNotification*)notify
{
NSMutableArray *receiveArray = [notify object];
......
NSInteger nNewsCount = receiveArray.count;
if (nNewsCount >0)
{
m_arrNews = receiveArray;//可以看到下面使用這個成員變量m_arrNews
}
[m_TableView reloadData];
......
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* strCellIdentifier =@"NewsCellIdentifier";
NewsListCell* cell = (NewsListCell *)[tableViewdequeueReusableCellWithIdentifier:strCellIdentifier];
if (cell == nil)
{
......
}
NSInteger row = indexPath.row;
if (m_arrNews)
{
//可以看到UITableView中的數據就是使用的m_arrNews
NewsData* data = (NewsData *)[m_arrNewsobjectAtIndex:row];
cell.Title = data.Title;
......
}
......
return cell;
}
看完這些,我也知道問題出在什麼地方了,我們知道,在UITableView中,在用戶拖動cell,有cell 的indexPath發生變化時,就會觸發這個函數:
[cpp] view plaincopy
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)這個函數中我們用到了m_arrNews,而m_arrNews又是通過
NSMutableArray *receiveArray = [notify object];
......
m_arrNews = receiveArray;
這麼來的,這裡面全是使用的指針拷貝,原來問題就是這個:淺拷貝,先來解釋一下深拷貝和淺拷貝:
(1)深拷貝,就是新拷貝一塊內存交給對象使用。會拷貝整個數據到新的地址,老的拷貝源改變和目標地址的數據就無關了。
(2)淺拷貝,就是覺得拷貝內存太浪費,直接給你我的地址吧。當然這個地址和拷貝源相同,只要拷貝源發生改變,這個目標地址中的數據也會變化。
問題就明顯了[newsDataArray removeAllObjects];導致了UITableView中的數據源發生變化,而大幅度拉動,導致了UITableView中數據刷新,進入
[cpp]
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *),數據失效,導致崩潰,那解決問題也很簡單,使用深拷貝就可以了。
- (void)onGetNewsListDataSuccess:(NSNotification*)notify
{
//NSMutableArray *receiveArray = [notify object];改成
NSMutableArray *receiveArray = [[NSArrayalloc]initWithArray:[notifyobject]];
......
NSInteger nNewsCount = receiveArray.count;
if (nNewsCount >0)
{
m_arrNews = receiveArray;//可以看到下面使用這個成員變量m_arrNews
}
[m_TableViewreloadData];
......
}