2025-02-10

我們習慣在開發中把數據和界面分開實現,這種方式比較好,隻需要在數據和界面中同時依賴一個數據結構即可,這種做法對於解藕是一個不錯的方式。
但是有一些細節的地方可能會導致我們遇到一些很難查找的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]; 
 
    …… 
 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *