好文章,轉載一下,有機會好好研究下
今天在研究SDWebImage和ASIHTTPRequest實現網絡圖片異步加載和本地緩存的時候,在UITableView顯示圖片的時候,出現了一些奇異的現象,比如:
1、TableView一次只能顯示10行的圖片,在所有圖片都加載完后,滾動TableView,讓隱藏在下面的行顯示在屏幕上,而這些行(比如11行)的圖像會先顯示第1行的圖片,然后在顯示屬于它自己的圖片。以此類推,后面的行都會出現這樣的問題!! 即使我們在所有行的圖片都還沒有下載完成的時候,滾動TableView,讓第11行、12行等出現在屏幕上,但它們依舊會先顯示錯誤的圖片,然后再顯示正確的圖片。
2、在ASIHTTPRequest的Demo中,當圖片加載后,滑動TableView,整個TableView的圖片將會亂掉,整個TableView以循環的方式顯示最后幾行的圖片。
一番查找后,發現之所以會出現這些問題,是因為我忽略了UITableView的重用機制的影響。進過適當的修改后,demo終于能夠正常運行了。下面是一些相關的資料和解決方法。
=======================================================================
iphone重用機制是蘋果為了實現大量數據顯示而采用的一種節省內存的機制,比如在UITableView和ScrollView 等地方。為什么要“可重用”???對于我們的項目來說,內存控制是必不可少的,如果一個tableview有幾百個cell,這個內存消耗是很大的,而且有些cell里面都有image之類的很占內存的資源存在的話,那這樣很容易出現memory warning甚至crash掉,這不是我們想要看到的。對此,tableview實現了它自己的管理方法dequeueReusableCellWithIdentifier(ps:我們在某些項目中scrollview來顯示很多張image,在scrollview滑動中也要這樣處理,來避免內存的過度消耗,只不過tableview它已經實現了這個方法,而不用我們自己去寫)。
但是在實際使用過程中,會有以下問題:
1、(蘋果文檔中不鼓勵我們在UITableViewCell中添加subView,最好采用自定義Cell,將需要的SubView添加到Cell當中。)使用addSubView在每項上添加視圖的時候會有重疊的現象。例如,UITableView中的Cell ,如果在cell上添加子視圖,則在使用蘋果的重用機制的時候,會重現子試圖重疊的現象。或出現開頭提到的兩個問題。如果在數據量不是很多的時候,可以手動屏蔽掉UITableView的重用機制。
這里不得不提一下UITableView的重用機制:
UITableView的重用機制的實現關鍵在于下面這個的函數:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
首先,我們要清楚這一點,這個函數是做什么的,它的文檔說明如下:
returns a reusable table-view cell object located by its identifier。它返回的是一個受identifier管理定位的可重用的tableViewCell,這里重點就在于“可重用”這3個字上。
我們來看它的實現方法,舉個例子來說,在系統剛啟動時,tableview可以顯示多少個cell,在這里我們假定為10個,在剛開始的時候tableview會生成10個tableviewcell,并且對應有自己的tag值,假定為0-9。(ps:蘋果官方的視頻中也提到了,盡量避免頻繁的add/remove view或者控件之類等。自定義啊自定義,相對于Android 空間的自定義,)所以采用下面的方法來實現:在tableview向上滾動的時候,tag為0的cell將不再顯示;然后我們把tag為0的cell移動到tag為9的cell下面,重新設置相關的屬性,然后將tag為1的cell移動到tag為0的cell下面……依此類推。這也就是所謂的“可重用”。
但是此時被移動的tag為0的cell的一些屬性還是保持不變的(包括之前添加的subView),因此就會出現一些無厘頭的bug(看了這么多,到這里是不是松了口氣? )。
接下來我們就要使用多種的方法來干掉這個重用機制:
(1):
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell != nil) {
[cell release];? //怎么樣?? 換了位置的Cell囂張不了了吧....
}
(2): //和(1)的方法本質一樣,略顯啰嗦。
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;
}
雖然干掉了重構機制,但我還是時不時地會想念它,特別是在數據多的時候,使用重用機制會好對你的程序的內存使用和優化都有很重要的作用。 但是這樣的話,如果想再cell上添加東西的話,重疊現象會很嚴重。好吧,魚和熊掌捆綁銷售啦啦!!使用xib給cell添加視圖來添加視圖吧......
具體步驟:
(1)新建一個基于UITableViewCell的類A和一個空白的xib。
(2)在A類中聲明要添加的視圖,例如IBOutlet UILabel *nameLabel,*timeLabel; ,注意:一要是使用? IBOutlet。
(3)將xib中的view刪除,拖一個 UITableViewCell,然后將這個UITableViewCell的類改為基于A。再把相應的視圖添在UITableViewCell上,并且與A類內定義的變量進行連接。這樣準備工作就完成了。
(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相應視圖的屬性就可以了。
補充:在使用地圖MKMapView一會使用到重用機制,如果想要在MKPinAnnotationView添加視圖的話,最好放棄那個重用機制,要不然效果會亂七八糟的(估計還有更好的處理方法)~~
什么,你不喜歡用Xib?好吧,這個老外寫的UITableView的代碼,估計會合你的口味,實現方式如下:
1、cell中的釋放
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] init....] autorelease];
UITextField *field = ...;//初始化
[cell addSubView:field];//添加
[field release];//釋放
}
2、通過遍歷修改UILabel屬性
UITextField*field = nil;
for(UIView *v in cell.contentView.subviews)
{
if([v isMemberOfClass:[UILabel class]])
field = (UITextField *)v;
}
......//接下來修改field的屬性
這種方法不敢說好不好,但是給我們提供了一種解決的思路,看大家的喜好了```
http://hi.baidu.com/%CB%E6%B7%E7_1989/blog/item/077c8a944ae7a69ca877a41d.html
(不貼這鏈接的話,這個隨風_1989估計饒不了我...)
原作者:http://blog.csdn.NET/joiningss/article/details/6702023
以下轉至:http://blog.csdn.net/omegayy/article/details/7356823
==========================================================
創建UITableViewController子類的實例后,IDE生成的代碼中有如下段落:
復制代碼
- (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;
}
復制代碼
這里就涉及了TableView的重用機制,為了做到顯示和數據分離,iOS tableView的實現并且不是為每個數據項創建一個tableCell。而是只創建屏幕可顯示最大個數的cell,然后重復使用這些cell,對cell做單獨的顯示配置,來達到既不影響顯示效果,又能充分節約內容的目的。下面簡要分析一下它的實現原理。
重用實現分析
查看UITableView頭文件,會找到NSMutableArray*? visiableCells,和NSMutableDictnery* reusableTableCells兩個結構。visiableCells內保存當前顯示的cells,reusableTableCells保存可重用的cells。
TableView顯示之初,reusableTableCells為空,那么tableView dequeueReusableCellWithIdentifier:CellIdentifier返回nil。開始的cell都是通過[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]來創建,而且cellForRowAtIndexPath只是調用最大顯示cell數的次數。
比如:有100條數據,iPhone一屏最多顯示10個cell。程序最開始顯示TableView的情況是:
1. 用[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]創建10次cell,并給cell指定同樣的重用標識(當然,可以為不同顯示類型的cell指定不同的標識)。并且10個cell全部都加入到visiableCells數組,reusableTableCells為空。
2. 向下拖動tableView,當cell1完全移出屏幕,并且cell11(它也是alloc出來的,原因同上)完全顯示出來的時候。cell11加入到visiableCells,cell1移出visiableCells,cell1加入到reusableTableCells。
3. 接著向下拖動tableView,因為reusableTableCells中已經有值,所以,當需要顯示新的cell,cellForRowAtIndexPath再次被調用的時候,tableView dequeueReusableCellWithIdentifier:CellIdentifier,返回cell1。cell1加入到visiableCells,cell1移出reusableTableCells;cell2移出visiableCells,cell2加入到reusableTableCells。之后再需要顯示的Cell就可以正常重用了。
所以整個過程并不難理解,但需要注意正是因為這樣的原因:配置Cell的時候一定要注意,對取出的重用的cell做重新賦值,不要遺留老數據。
一些情況
使用過程中,我注意到,并不是只有拖動超出屏幕的時候才會更新reusableTableCells表,還有:
1. reloadData,這種情況比較特殊。一般是部分數據發生變化,需要重新刷新cell顯示的內容時調用。在cellForRowAtIndexPath調用中,所有cell都是重用的。我估計reloadData調用后,把visiableCells中所有cell移入reusableTableCells,visiableCells清空。cellForRowAtIndexPath調用后,再把reuse的cell從reusableTableCells取出來,放入到visiableCells。
2. reloadRowsAtIndex,刷新指定的IndexPath。如果調用時reusableTableCells為空,那么cellForRowAtIndexPath調用后,是新創建cell,新的cell加入到visiableCells。老的cell移出visiableCells,加入到reusableTableCells。于是,之后的刷新就有cell做reuse了。
一些情況:
使用過程中,我注意到,并不是只有拖動超出屏幕的時候才會更新reusableTableCells表,還有:
1. reloadData,這種情況比較特殊。一般是部分數據發生變化,需要重新刷新cell顯示的內容時調用。在cellForRowAtIndexPath調用中,所有cell都是重用的。我估計reloadData調用后,把visiableCells中所有cell移入reusableTableCells,visiableCells清空。cellForRowAtIndexPath調用后,再把reuse的cell從reusableTableCells取出來,放入到visiableCells。
2. reloadRowsAtIndex,刷新指定的IndexPath。如果調用時reusableTableCells為空,那么cellForRowAtIndexPath調用后,是新創建cell,新的cell加入到visiableCells。老的cell移出visiableCells,加入到reusableTableCells。于是,之后的刷新就有cell做reuse了。
注意:
1-重取出來的cell是有可能已經捆綁過數據或者加過子視圖的,所以,如果有必要,要清除數據(比如textlabel的text)和remove掉add過的子視圖(使用tag)。
2-這樣設計的目的是為了避免頻繁的 alloc和delloc cell對象而已,沒有多復雜。
3-設計的關鍵是實現cell和數據的完全分離
重點:避免重用機制出錯
1.重用機制調用的就是dequeueReusableCellWithIdentifier這個方法,方法的意思就是“出列可重用的cell”,因而只要將它換為cellForRowAtIndexPath(只從要更新的cell的那一行取出cell),就可以不使用重用機制,因而問題就可以得到解決,但會浪費一些空間
2.為每個cell指定不同的重用標識符(reuseIdentifier)來解決。重用機制是根據相同的標識符來重用cell的,標識符不同的cell不能彼此重用。
[cpp] view plaincopy
NSString *identifier = [NSString stringWithFormat:@"TimeLineCell%d%d",indexPath.section,indexPath.row];
3.刪除重用的cell的所有子視圖,從而得到一個沒有特殊格式的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