關(guān)于uitableview自定義cell與重用機(jī)制探究

好文章,轉(zhuǎn)載一下,有機(jī)會(huì)好好研究下

今天在研究SDWebImage和ASIHTTPRequest實(shí)現(xiàn)網(wǎng)絡(luò)圖片異步加載和本地緩存的時(shí)候,在UITableView顯示圖片的時(shí)候,出現(xiàn)了一些奇異的現(xiàn)象,比如:

1、TableView一次只能顯示10行的圖片,在所有圖片都加載完后,滾動(dòng)TableView,讓隱藏在下面的行顯示在屏幕上,而這些行(比如11行)的圖像會(huì)先顯示第1行的圖片,然后在顯示屬于它自己的圖片。以此類推,后面的行都會(huì)出現(xiàn)這樣的問(wèn)題?。?即使我們?cè)谒行械膱D片都還沒(méi)有下載完成的時(shí)候,滾動(dòng)TableView,讓第11行、12行等出現(xiàn)在屏幕上,但它們依舊會(huì)先顯示錯(cuò)誤的圖片,然后再顯示正確的圖片。

2、在ASIHTTPRequest的Demo中,當(dāng)圖片加載后,滑動(dòng)TableView,整個(gè)TableView的圖片將會(huì)亂掉,整個(gè)TableView以循環(huán)的方式顯示最后幾行的圖片。

一番查找后,發(fā)現(xiàn)之所以會(huì)出現(xiàn)這些問(wèn)題,是因?yàn)槲液雎粤薝ITableView的重用機(jī)制的影響。進(jìn)過(guò)適當(dāng)?shù)男薷暮?,demo終于能夠正常運(yùn)行了。下面是一些相關(guān)的資料和解決方法。

=======================================================================

iphone重用機(jī)制是蘋果為了實(shí)現(xiàn)大量數(shù)據(jù)顯示而采用的一種節(jié)省內(nèi)存的機(jī)制,比如在UITableView和ScrollView 等地方。為什么要“可重用”???對(duì)于我們的項(xiàng)目來(lái)說(shuō),內(nèi)存控制是必不可少的,如果一個(gè)tableview有幾百個(gè)cell,這個(gè)內(nèi)存消耗是很大的,而且有些cell里面都有image之類的很占內(nèi)存的資源存在的話,那這樣很容易出現(xiàn)memory warning甚至crash掉,這不是我們想要看到的。對(duì)此,tableview實(shí)現(xiàn)了它自己的管理方法dequeueReusableCellWithIdentifier(ps:我們?cè)谀承╉?xiàng)目中scrollview來(lái)顯示很多張image,在scrollview滑動(dòng)中也要這樣處理,來(lái)避免內(nèi)存的過(guò)度消耗,只不過(guò)tableview它已經(jīng)實(shí)現(xiàn)了這個(gè)方法,而不用我們自己去寫)。

但是在實(shí)際使用過(guò)程中,會(huì)有以下問(wèn)題:

1、(蘋果文檔中不鼓勵(lì)我們?cè)赨ITableViewCell中添加subView,最好采用自定義Cell,將需要的SubView添加到Cell當(dāng)中。)使用addSubView在每項(xiàng)上添加視圖的時(shí)候會(huì)有重疊的現(xiàn)象。例如,UITableView中的Cell ,如果在cell上添加子視圖,則在使用蘋果的重用機(jī)制的時(shí)候,會(huì)重現(xiàn)子試圖重疊的現(xiàn)象?;虺霈F(xiàn)開頭提到的兩個(gè)問(wèn)題。如果在數(shù)據(jù)量不是很多的時(shí)候,可以手動(dòng)屏蔽掉UITableView的重用機(jī)制。

這里不得不提一下UITableView的重用機(jī)制:

UITableView的重用機(jī)制的實(shí)現(xiàn)關(guān)鍵在于下面這個(gè)的函數(shù):

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

首先,我們要清楚這一點(diǎn),這個(gè)函數(shù)是做什么的,它的文檔說(shuō)明如下:

returns a reusable table-view cell object located by its identifier。它返回的是一個(gè)受identifier管理定位的可重用的tableViewCell,這里重點(diǎn)就在于“可重用”這3個(gè)字上。

我們來(lái)看它的實(shí)現(xiàn)方法,舉個(gè)例子來(lái)說(shuō),在系統(tǒng)剛啟動(dòng)時(shí),tableview可以顯示多少個(gè)cell,在這里我們假定為10個(gè),在剛開始的時(shí)候tableview會(huì)生成10個(gè)tableviewcell,并且對(duì)應(yīng)有自己的tag值,假定為0-9。(ps:蘋果官方的視頻中也提到了,盡量避免頻繁的add/remove view或者控件之類等。自定義啊自定義,相對(duì)于Android 空間的自定義,)所以采用下面的方法來(lái)實(shí)現(xiàn):在tableview向上滾動(dòng)的時(shí)候,tag為0的cell將不再顯示;然后我們把tag為0的cell移動(dòng)到tag為9的cell下面,重新設(shè)置相關(guān)的屬性,然后將tag為1的cell移動(dòng)到tag為0的cell下面……依此類推。這也就是所謂的“可重用”。

但是此時(shí)被移動(dòng)的tag為0的cell的一些屬性還是保持不變的(包括之前添加的subView),因此就會(huì)出現(xiàn)一些無(wú)厘頭的bug(看了這么多,到這里是不是松了口氣? )。

接下來(lái)我們就要使用多種的方法來(lái)干掉這個(gè)重用機(jī)制:

(1):

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell != nil) {

[cell release];? //怎么樣?? 換了位置的Cell囂張不了了吧....

}

(2): //和(1)的方法本質(zhì)一樣,略顯啰嗦。

UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil) {

cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:CellIdentifier]autorelease];

}

NSArray*subviews = [[NSArrayalloc]initWithArray:cell.contentView.subviews];

for (UIView *subview in subviews) {

[subviewremoveFromSuperview];

}

[subviews release];

//customer

return cell;

}

(3)://丫的,組別都不一樣,看你怎么重用。

NSString *CellIdentifier = [NSString stringWithFormat:@"cell%d",indexPath.row];

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil)

{

........

}

else{

return cell;

}

雖然干掉了重構(gòu)機(jī)制,但我還是時(shí)不時(shí)地會(huì)想念它,特別是在數(shù)據(jù)多的時(shí)候,使用重用機(jī)制會(huì)好對(duì)你的程序的內(nèi)存使用和優(yōu)化都有很重要的作用。 但是這樣的話,如果想再cell上添加?xùn)|西的話,重疊現(xiàn)象會(huì)很嚴(yán)重。好吧,魚和熊掌捆綁銷售啦啦!!使用xib給cell添加視圖來(lái)添加視圖吧......

具體步驟:

(1)新建一個(gè)基于UITableViewCell的類A和一個(gè)空白的xib。

(2)在A類中聲明要添加的視圖,例如IBOutlet UILabel *nameLabel,*timeLabel; ,注意:一要是使用? IBOutlet。

(3)將xib中的view刪除,拖一個(gè) UITableViewCell,然后將這個(gè)UITableViewCell的類改為基于A。再把相應(yīng)的視圖添在UITableViewCell上,并且與A類內(nèi)定義的變量進(jìn)行連接。這樣準(zhǔn)備工作就完成了。

(4)使用方法:

A* cell = (A*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil) {

NSArray* nib = [[NSBundle mainBundle] loadNibNamed:@"VideoCell" owner:self options:nil];

cell = [nib objectAtIndex:0];

}

然后只需在下面改變cell相應(yīng)視圖的屬性就可以了。

補(bǔ)充:在使用地圖MKMapView一會(huì)使用到重用機(jī)制,如果想要在MKPinAnnotationView添加視圖的話,最好放棄那個(gè)重用機(jī)制,要不然效果會(huì)亂七八糟的(估計(jì)還有更好的處理方法)~~

什么,你不喜歡用Xib?好吧,這個(gè)老外寫的UITableView的代碼,估計(jì)會(huì)合你的口味,實(shí)現(xiàn)方式如下:

1、cell中的釋放

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil) {

cell = [[[UITableViewCell alloc] init....] autorelease];

UITextField *field = ...;//初始化

[cell addSubView:field];//添加

[field release];//釋放

}

2、通過(guò)遍歷修改UILabel屬性

UITextField*field = nil;

for(UIView *v in cell.contentView.subviews)

{

if([v isMemberOfClass:[UILabel class]])

field = (UITextField *)v;

}

......//接下來(lái)修改field的屬性

這種方法不敢說(shuō)好不好,但是給我們提供了一種解決的思路,看大家的喜好了```

http://hi.baidu.com/%CB%E6%B7%E7_1989/blog/item/077c8a944ae7a69ca877a41d.html

(不貼這鏈接的話,這個(gè)隨風(fēng)_1989估計(jì)饒不了我...)

原作者:http://blog.csdn.NET/joiningss/article/details/6702023

以下轉(zhuǎn)至:http://blog.csdn.net/omegayy/article/details/7356823

==========================================================

創(chuàng)建UITableViewController子類的實(shí)例后,IDE生成的代碼中有如下段落:

復(fù)制代碼

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = [NSString stringWithFormat:@"Cell"];

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];

}

//config the cell

return cell;

}

復(fù)制代碼

這里就涉及了TableView的重用機(jī)制,為了做到顯示和數(shù)據(jù)分離,iOS tableView的實(shí)現(xiàn)并且不是為每個(gè)數(shù)據(jù)項(xiàng)創(chuàng)建一個(gè)tableCell。而是只創(chuàng)建屏幕可顯示最大個(gè)數(shù)的cell,然后重復(fù)使用這些cell,對(duì)cell做單獨(dú)的顯示配置,來(lái)達(dá)到既不影響顯示效果,又能充分節(jié)約內(nèi)容的目的。下面簡(jiǎn)要分析一下它的實(shí)現(xiàn)原理。

重用實(shí)現(xiàn)分析

查看UITableView頭文件,會(huì)找到NSMutableArray*? visiableCells,和NSMutableDictnery* reusableTableCells兩個(gè)結(jié)構(gòu)。visiableCells內(nèi)保存當(dāng)前顯示的cells,reusableTableCells保存可重用的cells。

TableView顯示之初,reusableTableCells為空,那么tableView dequeueReusableCellWithIdentifier:CellIdentifier返回nil。開始的cell都是通過(guò)[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]來(lái)創(chuàng)建,而且cellForRowAtIndexPath只是調(diào)用最大顯示cell數(shù)的次數(shù)。

比如:有100條數(shù)據(jù),iPhone一屏最多顯示10個(gè)cell。程序最開始顯示TableView的情況是:

1. 用[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]創(chuàng)建10次cell,并給cell指定同樣的重用標(biāo)識(shí)(當(dāng)然,可以為不同顯示類型的cell指定不同的標(biāo)識(shí))。并且10個(gè)cell全部都加入到visiableCells數(shù)組,reusableTableCells為空。

2. 向下拖動(dòng)tableView,當(dāng)cell1完全移出屏幕,并且cell11(它也是alloc出來(lái)的,原因同上)完全顯示出來(lái)的時(shí)候。cell11加入到visiableCells,cell1移出visiableCells,cell1加入到reusableTableCells。

3. 接著向下拖動(dòng)tableView,因?yàn)閞eusableTableCells中已經(jīng)有值,所以,當(dāng)需要顯示新的cell,cellForRowAtIndexPath再次被調(diào)用的時(shí)候,tableView dequeueReusableCellWithIdentifier:CellIdentifier,返回cell1。cell1加入到visiableCells,cell1移出reusableTableCells;cell2移出visiableCells,cell2加入到reusableTableCells。之后再需要顯示的Cell就可以正常重用了。

所以整個(gè)過(guò)程并不難理解,但需要注意正是因?yàn)檫@樣的原因:配置Cell的時(shí)候一定要注意,對(duì)取出的重用的cell做重新賦值,不要遺留老數(shù)據(jù)。

一些情況

使用過(guò)程中,我注意到,并不是只有拖動(dòng)超出屏幕的時(shí)候才會(huì)更新reusableTableCells表,還有:

1. reloadData,這種情況比較特殊。一般是部分?jǐn)?shù)據(jù)發(fā)生變化,需要重新刷新cell顯示的內(nèi)容時(shí)調(diào)用。在cellForRowAtIndexPath調(diào)用中,所有cell都是重用的。我估計(jì)reloadData調(diào)用后,把visiableCells中所有cell移入reusableTableCells,visiableCells清空。cellForRowAtIndexPath調(diào)用后,再把reuse的cell從reusableTableCells取出來(lái),放入到visiableCells。

2. reloadRowsAtIndex,刷新指定的IndexPath。如果調(diào)用時(shí)reusableTableCells為空,那么cellForRowAtIndexPath調(diào)用后,是新創(chuàng)建cell,新的cell加入到visiableCells。老的cell移出visiableCells,加入到reusableTableCells。于是,之后的刷新就有cell做reuse了。

一些情況:

使用過(guò)程中,我注意到,并不是只有拖動(dòng)超出屏幕的時(shí)候才會(huì)更新reusableTableCells表,還有:

1. reloadData,這種情況比較特殊。一般是部分?jǐn)?shù)據(jù)發(fā)生變化,需要重新刷新cell顯示的內(nèi)容時(shí)調(diào)用。在cellForRowAtIndexPath調(diào)用中,所有cell都是重用的。我估計(jì)reloadData調(diào)用后,把visiableCells中所有cell移入reusableTableCells,visiableCells清空。cellForRowAtIndexPath調(diào)用后,再把reuse的cell從reusableTableCells取出來(lái),放入到visiableCells。

2. reloadRowsAtIndex,刷新指定的IndexPath。如果調(diào)用時(shí)reusableTableCells為空,那么cellForRowAtIndexPath調(diào)用后,是新創(chuàng)建cell,新的cell加入到visiableCells。老的cell移出visiableCells,加入到reusableTableCells。于是,之后的刷新就有cell做reuse了。

注意:

1-重取出來(lái)的cell是有可能已經(jīng)捆綁過(guò)數(shù)據(jù)或者加過(guò)子視圖的,所以,如果有必要,要清除數(shù)據(jù)(比如textlabel的text)和remove掉add過(guò)的子視圖(使用tag)。

2-這樣設(shè)計(jì)的目的是為了避免頻繁的 alloc和delloc cell對(duì)象而已,沒(méi)有多復(fù)雜。

3-設(shè)計(jì)的關(guān)鍵是實(shí)現(xiàn)cell和數(shù)據(jù)的完全分離

重點(diǎn):避免重用機(jī)制出錯(cuò)

1.重用機(jī)制調(diào)用的就是dequeueReusableCellWithIdentifier這個(gè)方法,方法的意思就是“出列可重用的cell”,因而只要將它換為cellForRowAtIndexPath(只從要更新的cell的那一行取出cell),就可以不使用重用機(jī)制,因而問(wèn)題就可以得到解決,但會(huì)浪費(fèi)一些空間

2.為每個(gè)cell指定不同的重用標(biāo)識(shí)符(reuseIdentifier)來(lái)解決。重用機(jī)制是根據(jù)相同的標(biāo)識(shí)符來(lái)重用cell的,標(biāo)識(shí)符不同的cell不能彼此重用。

[cpp] view plaincopy

NSString *identifier = [NSString stringWithFormat:@"TimeLineCell%d%d",indexPath.section,indexPath.row];

3.刪除重用的cell的所有子視圖,從而得到一個(gè)沒(méi)有特殊格式的cell,供其他cell重用。

[cpp] view plaincopy

if (cell == nil) {

cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];

}

else

{

//刪除cell的所有子視圖

while ([cell.contentView.subviews lastObject] != nil)

{

[(UIView*)[cell.contentView.subviews lastObject] removeFromSuperview];

}

}

原文章http://blog.csdn.net/xy603876399/article/details/9968561

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容