UITableView重用機(jī)制和原理
cell = [tableView dequeueReusableCellWithIdentifier:identifier];
滑動(dòng)過(guò)程中,A3,A4,A5全部顯示在屏幕上,A1移除屏幕時(shí),就被放到重用池中,當(dāng)A7即將顯示在屏幕上時(shí),從重用池中根據(jù)指定的≈取出可重用的cell,
如果A1-A7為同一種identifier標(biāo)識(shí)符的話,A7就可以復(fù)用A1所創(chuàng)建的cell的內(nèi)存或者控件,就達(dá)到了cell復(fù)用的機(jī)制
實(shí)現(xiàn)一個(gè)tableView的索引表的重用池 (Demo百度網(wǎng)盤(pán))
默認(rèn)一加載是6個(gè)索引,當(dāng)點(diǎn)擊頁(yè)面上一個(gè)按鈕時(shí),變成10個(gè)索引,在此點(diǎn)擊變回6個(gè)索引,依次循環(huán)

1.按上圖自定義tableview,添加變量containerView和reusePool,其中containerView添加在tableView的最上方, reusePool繼承NSObject,用它來(lái)實(shí)現(xiàn)復(fù)用池
2.復(fù)用池用兩個(gè)NSMutableSet集合來(lái)實(shí)現(xiàn),一個(gè)為usingQueue,一個(gè)為waitUsedQueue
3.在tableView的reloadData方法里面(這個(gè)方法在數(shù)據(jù)源一開(kāi)始會(huì)調(diào)用一次,后面手動(dòng)調(diào)用),懶加載初始化reusePool,首先按照?qǐng)D一重置兩個(gè)隊(duì)列
然后通過(guò)代理,從VC中獲取索引數(shù)組(6或者10)
循環(huán)生成數(shù)組個(gè)數(shù)的button添加到contentview上顯示,這一步用的是復(fù)用池
4.復(fù)用池是,每次循環(huán),都按照?qǐng)D二,首先從等待隊(duì)列中取button,如果取到了,將它移動(dòng)到使用隊(duì)列中并使用,若沒(méi)取到,創(chuàng)建一個(gè)button并將它添加到使用隊(duì)列中使用
5.每次刷新,重走3和4,則實(shí)現(xiàn)了一直復(fù)用的效果
備注:圖三即為,初始是索引為6,全部為新建的,點(diǎn)擊按鈕reloadData,索引為11
則有6個(gè)是復(fù)用的,剩下的為新建的,此時(shí)隊(duì)列中有11個(gè)button
再點(diǎn)擊按鈕reloadData,索引為6,全部復(fù)用
.....
下面兩圖為每次reloadDate時(shí)復(fù)用池的工作過(guò)程



#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
// 實(shí)現(xiàn)重用機(jī)制的類
@interface ViewReusePool : NSObject
// 從重用池當(dāng)中取出一個(gè)可重用的view
- (UIView *)dequeueReusableView;
// 向重用池當(dāng)中添加一個(gè)視圖
- (void)addUsingView:(UIView *)view;
// 重置方法,將當(dāng)前使用中的視圖移動(dòng)到可重用隊(duì)列當(dāng)中
- (void)reset;
@end
#import "ViewReusePool.h"
@interface ViewReusePool ()
// 等待使用的隊(duì)列
@property (nonatomic, strong) NSMutableSet *waitUsedQueue;
// 使用中的隊(duì)列
@property (nonatomic, strong) NSMutableSet *usingQueue;
@end
@implementation ViewReusePool
- (id)init{
self = [super init];
if (self) {
_waitUsedQueue = [NSMutableSet set];
_usingQueue = [NSMutableSet set];
}
return self;
}
// 從重用池當(dāng)中取出一個(gè)可重用的view
- (UIView *)dequeueReusableView{
UIView *view = [_waitUsedQueue anyObject];
if (view == nil) {
return nil;
}
else{
// 進(jìn)行隊(duì)列移動(dòng)
[_waitUsedQueue removeObject:view];
[_usingQueue addObject:view];
return view;
}
}
// 向重用池當(dāng)中添加一個(gè)視圖
- (void)addUsingView:(UIView *)view
{
if (view == nil) {
return;
}
// 添加視圖到使用中的隊(duì)列
[_usingQueue addObject:view];
}
// 重置方法,將當(dāng)前使用中的視圖移動(dòng)到可重用隊(duì)列當(dāng)中
- (void)reset{
UIView *view = nil;
while ((view = [_usingQueue anyObject])) {
// 從使用中隊(duì)列移除
[_usingQueue removeObject:view];
// 加入等待使用的隊(duì)列
[_waitUsedQueue addObject:view];
}
}
@end
每次reloadData時(shí)
// 標(biāo)記所有視圖為可重用狀態(tài)
[reusePool reset];
//
NSUInteger count = arrayTitles.count;
CGFloat buttonWidth = 60;
CGFloat buttonHeight = self.frame.size.height / count;
for (int i = 0; i < [arrayTitles count]; i++) {
NSString *title = [arrayTitles objectAtIndex:i];
// 從重用池當(dāng)中取一個(gè)Button出來(lái)
UIButton *button = (UIButton *)[reusePool dequeueReusableView];
// 如果沒(méi)有可重用的Button重新創(chuàng)建一個(gè)
if (button == nil) {
button = [[UIButton alloc] initWithFrame:CGRectZero];
button.backgroundColor = [UIColor whiteColor];
// 注冊(cè)button到重用池當(dāng)中
[reusePool addUsingView:button];
NSLog(@"新創(chuàng)建一個(gè)Button");
}
else{
NSLog(@"Button 重用了");
}
// 添加button到父視圖控件
[containerView addSubview:button];
[button setTitle:title forState:UIControlStateNormal];
[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
// 設(shè)置button的坐標(biāo)
[button setFrame:CGRectMake(0, i * buttonHeight, buttonWidth, buttonHeight)];
}
reloadData
reloadData是異步繪制的
[tableView reloadData]并不會(huì)等待tableview更新結(jié)束后才執(zhí)行后續(xù)代碼
而是立即執(zhí)行后續(xù)代碼,然后異步地去計(jì)算tableView的高度,獲取cell等等
如果表中的數(shù)據(jù)非常大,在一個(gè)run loop周期沒(méi)執(zhí)行完,
這時(shí)就顯示tableView視圖數(shù)據(jù)的操作就會(huì)出問(wèn)題了。
解決方法是:
1. 通過(guò)layoutIfNeeded方法,強(qiáng)制重繪并等待完成。
[self.tableView reloadData];
[self.tableView layoutIfNeeded];
//刷新完成,執(zhí)行后續(xù)需要執(zhí)行的代碼
2.reloadData方法會(huì)在主線程執(zhí)行,通過(guò)GCD,使后續(xù)操作排隊(duì)在reloadData后面執(zhí)行。
[self.tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
//刷新完成,執(zhí)行后續(xù)代碼
});
數(shù)據(jù)源同步


我們?cè)谒⑿聰?shù)據(jù)時(shí)會(huì)面臨下面的問(wèn)題,某些用戶交互的操作是在主線程進(jìn)行的,而網(wǎng)絡(luò)和數(shù)據(jù)解析是在子線程進(jìn)行的,當(dāng)主線程做了例如刪除行A的操作并刷新了UI后,若之后子線程數(shù)據(jù)返回,因?yàn)樽泳€程拿到的是刪除前的數(shù)據(jù)拷貝,當(dāng)它處理完數(shù)據(jù)后,返回到主線程刷新UI,因?yàn)榇藭r(shí)子線程是有行A的,但主線程是沒(méi)有行A的,刷新后主線程就會(huì)又出現(xiàn)行A,此時(shí)就會(huì)出現(xiàn)數(shù)據(jù)問(wèn)題
方案一:并發(fā)訪問(wèn),數(shù)據(jù)拷貝,會(huì)增加內(nèi)存開(kāi)銷
在主線程刪除時(shí)記錄下來(lái),之后在子線程返回?cái)?shù)據(jù)將要更新UI時(shí),同步一下記錄的刪除操作,也就是在子線程中也刪除,再回到主線程就是同步的

方案二:串行訪問(wèn),若子線程的數(shù)據(jù)解析很慢,那么主線程的刪除操作就會(huì)有延遲
