UITableView重用機制权均、自定義重用池以及數(shù)據(jù)源同步

一.UITableView的重用機制

1.重用原理

重用方法:

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];

如下圖顿膨,假設(shè)虛線范圍是屏幕的顯示區(qū)域锅锨;
A2、A6的cell有一部分在屏幕內(nèi)
A3恋沃、A4必搞、A5的cell全在屏幕內(nèi)
A1在屏幕外,現(xiàn)在它就被放到了重用池囊咏;
如果整個屏幕里面每個cell的identifier是一樣的恕洲,向上滑動的時候,A7就會去重用池里面取出A1存放的cell梅割;

簡單的說:就如同盤子使用了之后霜第,洗完可以繼續(xù)用;

示例

2.手寫重用

目的:1.熟悉重用户辞;2.會自定義重用

實現(xiàn)要求:
給tableview增加自定義索引泌类,點擊按鈕可以切換索引的內(nèi)容;
索引上面的按鈕要進行復(fù)用底燎;
如下:點擊紅色按鈕刃榨,可以切換右側(cè)的索引條內(nèi)容。


效果

實現(xiàn)思路:
1.自定義UITableView双仍,給UITableView增加索引條containerView枢希;UITableView負責數(shù)據(jù)顯示和布局;
2.containerView的按鈕從ViewReusePool中獲取

實現(xiàn)思路

2.1重用的類ViewReusePool

重用池:
記錄正在使用殊校、等待使用的view晴玖;
提供獲取等待使用的view方法(取)为流、向重用池添加視圖的方法(放)呕屎、將所有視圖移動到重用隊列的方法(刪除);

補充:這里的數(shù)據(jù)用NSSet保存敬察;因為我們這里只是從重用池隨機取出一個可用的就好了秀睛。NSSet可以提高讀取效率;

NSArray NSSet
有序 無序
可以有相同的對象 有唯一的對象(重復(fù)的對象會被去掉)
通過索引來提取對象 通過對比來提取對象

NSSet的效率確實是比NSArray高的莲祸,因為它主要用的是hash算
NSArray的話需要循環(huán)集合中所有的對象蹂安,來找到所需要的目標。所以锐帜,循環(huán)所有對象與直接去對象的位置獲取田盈,速度就顯而易見了。
iOS_NSSet與NSArray的區(qū)別

.h:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
// 實現(xiàn)重用機制的類
@interface ViewReusePool : NSObject

// 從重用池當中取出一個可重用的view
- (UIView *)dequeueReusableView;

// 向重用池當中添加一個視圖
- (void)addUsingView:(UIView *)view;

// 重置方法缴阎,將當前使用中的視圖移動到可重用隊列當中
- (void)reset;

@end

.m
#import "ViewReusePool.h"

@interface ViewReusePool ()
// 等待使用的隊列
@property (nonatomic, strong) NSMutableSet *waitUsedQueue;
// 使用中的隊列
@property (nonatomic, strong) NSMutableSet *usingQueue;
@end

@implementation ViewReusePool

- (id)init{
    self = [super init];
    if (self) {
        _waitUsedQueue = [NSMutableSet set];
        _usingQueue = [NSMutableSet set];
    }
    return self;
}

- (UIView *)dequeueReusableView{
    UIView *view = [_waitUsedQueue anyObject];
    if (view == nil) {
        return nil;
    }
    else{
        // 進行隊列移動
        [_waitUsedQueue removeObject:view];
        [_usingQueue addObject:view];
        return view;
    }
}

- (void)addUsingView:(UIView *)view
{
    if (view == nil) {
        return;
    }
    
    // 添加視圖到使用中的隊列
    [_usingQueue addObject:view];
}

- (void)reset{
    UIView *view = nil;
    while ((view = [_usingQueue anyObject])) {
        // 從使用中隊列移除
        [_usingQueue removeObject:view];
        // 加入等待使用的隊列
        [_waitUsedQueue addObject:view];
    }
}

@end

2.2 自定義UITableView

.h:
#import <UIKit/UIKit.h>

//通過協(xié)議獲取索引條顯示的數(shù)據(jù)
@protocol IndexedTableViewDataSource <NSObject>

// 獲取一個tableview的字母索引條數(shù)據(jù)的方法
- (NSArray <NSString *> *)indexTitlesForIndexTableView:(UITableView *)tableView;

@end

@interface IndexedTableView : UITableView
@property (nonatomic, weak) id <IndexedTableViewDataSource> indexedDataSource;
@end

.m:
#import "IndexedTableView.h"
#import "ViewReusePool.h"
@interface IndexedTableView ()
{
    UIView *containerView;
    ViewReusePool *reusePool;
}
@end

@implementation IndexedTableView

- (void)reloadData{
    [super reloadData];
    
    // 懶加載(當需要的時候再創(chuàng)建)
    if (containerView == nil) {
        containerView = [[UIView alloc] initWithFrame:CGRectZero];
        containerView.backgroundColor = [UIColor whiteColor];
        
        //  [self addSubview:containerView];//如果這樣寫允瞧,tableview滾動的時候containerView也會滾動
        //避免索引條隨著table滾動
        [self.superview insertSubview:containerView aboveSubview:self];
    }
    
    if (reusePool == nil) {
        reusePool = [[ViewReusePool alloc] init];
    }
    
    // 標記所有視圖為可重用狀態(tài)
    [reusePool reset];
    
    // reload字母索引條
    [self reloadIndexedBar];
}

- (void)reloadIndexedBar
{
    // 獲取字母索引條的顯示內(nèi)容
    NSArray <NSString *> *arrayTitles = nil;
    if ([self.indexedDataSource respondsToSelector:@selector(indexTitlesForIndexTableView:)]) {
        arrayTitles = [self.indexedDataSource indexTitlesForIndexTableView:self];
    }
    
    // 判斷字母索引條是否為空
    if (!arrayTitles || arrayTitles.count <= 0) {
        [containerView setHidden:YES];
        return;
    }
    
    NSUInteger count = arrayTitles.count;
    CGFloat buttonWidth = 60;
    CGFloat buttonHeight = self.frame.size.height / count;

    //移除之前view上的所有數(shù)據(jù)
    [containerView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
    
    for (int i = 0; i < [arrayTitles count]; i++) {
        NSString *title = [arrayTitles objectAtIndex:i];
        
        // 從重用池當中取一個Button出來
        UIButton *button = (UIButton *)[reusePool dequeueReusableView];
        // 如果沒有可重用的Button重新創(chuàng)建一個
        if (button == nil) {
            button = [[UIButton alloc] initWithFrame:CGRectZero];
            button.backgroundColor = [UIColor whiteColor];
            
            // 注冊button到重用池當中
            [reusePool addUsingView:button];
            NSLog(@"新創(chuàng)建一個Button");
        }
        else{
            NSLog(@"Button 重用了");
        }
        
        // 添加button到父視圖控件
        [containerView addSubview:button];
        [button setTitle:title forState:UIControlStateNormal];
        [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        
        // 設(shè)置button的坐標
        [button setFrame:CGRectMake(0, i * buttonHeight, buttonWidth, buttonHeight)];
    }
    
    [containerView setHidden:NO];
    containerView.frame = CGRectMake(self.frame.origin.x + self.frame.size.width - buttonWidth, self.frame.origin.y, buttonWidth, self.frame.size.height);
}


@end

2.3 controller使用自定義UITableView

.m

#import "ViewController.h"
#import "IndexedTableView.h"
@interface ViewController ()<UITableViewDataSource,UITableViewDelegate,IndexedTableViewDataSource>
{
    IndexedTableView *tableView;//帶有索引條的tableview
    UIButton *button;
    NSMutableArray *dataSource;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //創(chuàng)建一個Tableview
    tableView = [[IndexedTableView alloc] initWithFrame:CGRectMake(0, 60, self.view.frame.size.width, self.view.frame.size.height - 60) style:UITableViewStylePlain];
    tableView.delegate = self;
    tableView.dataSource = self;
    
    // 設(shè)置table的索引數(shù)據(jù)源
    tableView.indexedDataSource = self;
    
    [self.view addSubview:tableView];
    
    //創(chuàng)建一個按鈕
    button = [[UIButton alloc] initWithFrame:CGRectMake(0, 20, self.view.frame.size.width, 40)];
    button.backgroundColor = [UIColor redColor];
    [button setTitle:@"reloadTable" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(doAction:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    
    // 數(shù)據(jù)源
    dataSource = [NSMutableArray array];
    for (int i = 0; i < 100; i++) {
        [dataSource addObject:@(i+1)];
    }
    // Do any additional setup after loading the view, typically from a nib.
    
}

#pragma mark IndexedTableViewDataSource

- (NSArray <NSString *> *)indexTitlesForIndexTableView:(UITableView *)tableView{
    
    //奇數(shù)次調(diào)用返回6個字母,偶數(shù)次調(diào)用返回11個
    static BOOL change = NO;
    
    if (change) {
        change = NO;
        return @[@"A",@"B",@"C",@"D",@"E",@"F",@"G",@"H",@"I",@"J",@"K"];
    }
    else{
        change = YES;
        return @[@"A",@"B",@"C",@"D",@"E",@"F"];
    }
    
}

#pragma mark UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [dataSource count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *identifier = @"reuseId";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    //如果重用池當中沒有可重用的cell,那么創(chuàng)建一個cell
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
    }
    // 文案設(shè)置
    cell.textLabel.text = [[dataSource objectAtIndex:indexPath.row] stringValue];
    
    //返回一個cell
    return cell;
}

#pragma mark - UITableViewDelegate

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 40;
}

- (void)doAction:(id)sender{
    NSLog(@"reloadData");
    [tableView reloadData];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

3.手寫重用池總結(jié)

1述暂、懶加載(在真正需要的時候創(chuàng)建痹升,以前并未注意這一條)
2、給自定義view提供數(shù)據(jù)的方法畦韭,之前使用的是屬性view.array = array;疼蛾,今天學(xué)習到也可以使用代理;
3艺配、要合適的自定義view察郁,

  • 之前的寫法:
    在controller里面添加TableView,然后在controller的view上面添加索引的view妒挎;

缺點:如果要索引條去掉绳锅,就需要刪除controller里面的大量代碼;

  • 今天學(xué)到的:
    定義IndextTableView繼承 UITableView酝掩,在IndextTableView里面添加索引view鳞芙;
    在contrlloer里面使用,創(chuàng)建UITableView的時候類型修改為IndexedTableView,實現(xiàn)IndexedTableView為索引提供數(shù)據(jù)的代理:
    tableView = [[IndexedTableView alloc] initWithFrame:CGRectMake(0, 60, self.view.frame.size.width, self.view.frame.size.height - 60) style:UITableViewStylePlain];
    tableView.delegate = self;
    tableView.dataSource = self;
    
    // 設(shè)置table的索引數(shù)據(jù)源
    tableView.indexedDataSource = self;
    
    [self.view addSubview:tableView];

優(yōu)點:
如果要索引條去掉
如果在controller里面創(chuàng)建UITableView的時候更改類型期虾,把為索引提供數(shù)據(jù)的代理去掉即可原朝。

4、重用池這部分代碼镶苞,以后直接文件拷出來用也可以喳坠;

二.數(shù)據(jù)源同步

例如數(shù)據(jù)刪除的時候,會有數(shù)據(jù)源同步的問題茂蚓;

數(shù)據(jù)源同步問題

場景:如下圖壕鹉,在子線程里面下拉刷新請求數(shù)據(jù)的時候,主線程刪除了一個一條數(shù)據(jù)聋涨,刷新UI顯示沒有問題晾浴;
但是請求下來的數(shù)據(jù)里面可能還包含刪除掉的那條數(shù)據(jù);刷新數(shù)據(jù)的時候就會有問題牍白,怎么解決這種數(shù)據(jù)源同步的問題呢脊凰?

數(shù)據(jù)不同步的場景

解決:
方案1:并發(fā)訪問、數(shù)據(jù)拷貝
方案2:串行訪問

方案1:并發(fā)訪問茂腥、數(shù)據(jù)拷貝

如果進行并發(fā)訪問狸涌,刪除掉數(shù)據(jù)之后進行記錄;等待數(shù)據(jù)請求回來后最岗,把刪除掉的數(shù)據(jù)從請求回來的數(shù)據(jù)里面刪除帕胆;

并發(fā)訪問解決方案

方案2:串行訪問

采取串行訪問,在數(shù)據(jù)請求的時候不可以進行刪除般渡,等待數(shù)據(jù)訪問回來之后才可以進行刪除惶楼;

串行訪問解決方案

兩種方案優(yōu)缺點對比

  • 并發(fā)訪問:記錄之后右蹦,遍歷并刪除數(shù)據(jù)這部分會有內(nèi)存、時間消耗
  • 串行訪問:等待數(shù)據(jù)請求完成之后再刪除歼捐,就會讓用戶進行等待
    需要根據(jù)實際情況選擇解決方案;

三.補充:

UITableView代理方法調(diào)用順序:

UITableView是繼承自UIScrollView的晨汹,需要先確定它的contentSize及每個Cell的位置豹储。
所以UITableView的回調(diào)順序是先多次調(diào)用tableView:heightForRowAtIndexPath:以確定contentSize及Cell的位置,然后才會調(diào)用tableView:cellForRowAtIndexPath:淘这,從而來顯示在當前屏幕的Cell

舉個例子來說:如果現(xiàn)在要顯示100個Cell剥扣,當前屏幕顯示5個。那么刷新(reload)UITableView時铝穷,UITableView會先調(diào)用100次tableView:heightForRowAtIndexPath:方法钠怯,然后調(diào)用5次tableView:cellForRowAtIndexPath:方法;滾動屏幕時曙聂,每當Cell滾入屏幕晦炊,都會調(diào)用一次tableView:heightForRowAtIndexPath:、tableView:cellForRowAtIndexPath:方法宁脊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末断国,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子榆苞,更是在濱河造成了極大的恐慌稳衬,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坐漏,死亡現(xiàn)場離奇詭異薄疚,居然都是意外死亡,警方通過查閱死者的電腦和手機赊琳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門街夭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慨畸,你說我怎么就攤上這事莱坎。” “怎么了寸士?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵檐什,是天一觀的道長。 經(jīng)常有香客問我弱卡,道長乃正,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任婶博,我火速辦了婚禮瓮具,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己名党,他們只是感情好叹阔,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著传睹,像睡著了一般耳幢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上欧啤,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天睛藻,我揣著相機與錄音,去河邊找鬼邢隧。 笑死店印,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的倒慧。 我是一名探鬼主播按摘,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼迫靖!你這毒婦竟也來了院峡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤系宜,失蹤者是張志新(化名)和其女友劉穎照激,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盹牧,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡俩垃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了汰寓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片口柳。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖有滑,靈堂內(nèi)的尸體忽然破棺而出跃闹,到底是詐尸還是另有隱情,我是刑警寧澤毛好,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布望艺,位于F島的核電站,受9級特大地震影響肌访,放射性物質(zhì)發(fā)生泄漏找默。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一吼驶、第九天 我趴在偏房一處隱蔽的房頂上張望惩激。 院中可真熱鬧店煞,春花似錦、人聲如沸风钻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骡技。三九已至衩椒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哮兰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工苟弛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留喝滞,地道東北人。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓膏秫,卻偏偏與公主長得像右遭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缤削,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容