tableView原理分析

分析原生TableView數(shù)據(jù)加載過程

圖一

當tableView走reloadData方法的時候,走的方法順序如下:

  • numberOfSectionsInTableView
  • numberOfRowsInSection
  • heightForRowAtIndexPath
  • cellForRowAtIndexPath
  • heightForRowAtIndexPath

會先實現(xiàn)一個tableView中有幾組數(shù)據(jù),然后每組有多少行,然后再每組默認的高度,然后再加載cell,最后再重新返回一次準確的高度,并且加載的也是當前頁面的cell,超過頁面的不會加載。
其中當?shù)谝淮芜\行tableView的時候,即一開始進來,tableView展示數(shù)據(jù),而不是走reloadData方法的時候,上面圖一中紅色框的方法會多走兩遍,而且就算是有多組,每組也會多走兩遍。
如果每個cell的高度不一,需要動態(tài)計算cell的高度,我們可以第一次算出來過后將cell的高度保存,避免每次滑動tableView,或者reloadData的時候都再計算一次cell的高度。

模仿tableView

由系統(tǒng)的可知,要實現(xiàn)一個tableView,需要繼承自UIScrollView,并且定義一個tableView代理和代理中必須實現(xiàn)的方法

#import <UIKit/UIKit.h>
#import "EOCTableViewCell.h"

@class EOCTableView;
@protocol EOCTableViewDelegate <NSObject>
@required
- (NSInteger)eocTableView:(EOCTableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (CGFloat)eocTableView:(EOCTableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (EOCTableViewCell *)eocTableView:(EOCTableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
@end

@interface EOCTableView : UIScrollView{
    NSMutableArray *_rowModeAry;                  //裝cell的起始Y值和高度的模型數(shù)組
    NSMutableArray *_reuseCellPoolArr;            //重用池
    NSMutableDictionary *_visibleCellPoolDict;    //現(xiàn)有池
}

@property (nonatomic, weak)id<EOCTableViewDelegate> eocDelegate;
// 從重用池拿cell的方法
- (EOCTableViewCell *)dequeueReusableCellWithIdentifier:(NSString*)identifier;
- (void)reloadData;
@end

還需要自定義一個默認有l(wèi)abel的tableViewCell,而且還必須要綁定identifier屬性

#import <UIKit/UIKit.h>

@interface EOCTableViewCell : UIView

- (id)initWithIdentifier:(NSString*)identifier;
@property (nonatomic, strong)UILabel *textLabel;
@property (nonatomic, strong)NSString *identifier;

@end
#import "EOCTableViewCell.h"

@implementation EOCTableViewCell

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        _textLabel = [[UILabel alloc] init];
        [self addSubview:_textLabel];
    }
    return self;
}
- (id)initWithIdentifier:(NSString*)identifier
{
    self = [super init];
    if (self) {
        _identifier = identifier;
    }
    return self;
}

- (void)layoutSubviews
{
    [super layoutSubviews];
    [_textLabel setFrame:CGRectMake(15, 0, self.frame.size.width-15, self.frame.size.height)];
}
@end

還需要一個用于保存每個cell的identifier和起始Y坐標和cell高度的模型Model

#import <Foundation/Foundation.h>

@interface RowInfoModel : NSObject
@property (nonatomic, strong)NSString *identifi;
@property (nonatomic, assign)float originY;
@property (nonatomic, assign)float sizeHeight;
@end

.m文件中不做什么操作

reloadData方法的內(nèi)部實現(xiàn)
- (void)reloadData{
    
    [self countRowPosition];// 數(shù)據(jù)準備
    [self setNeedsLayout];// setNeedsLayout不會在當前事件循環(huán)操作,會放在下一個事件循環(huán)
}

// 保存postion數(shù)據(jù)
- (void)countRowPosition{
    // 先清空原數(shù)據(jù),避免疊加
    [_rowModeAry removeAllObjects];
    float addUpHigh = 0;
    
    for (int i = 0; i < [_eocDelegate eocTableView:self numberOfRowsInSection:0]; i++) {
       
        NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0];
        //有多少cell就要先走多少遍heightForRowAtIndexPath這個方法
        float cellHigh = [_eocDelegate eocTableView:self heightForRowAtIndexPath:path];
        RowInfoModel *rowModel = [[RowInfoModel alloc] init];
        rowModel.originY = addUpHigh;   //記錄每個cell的初始Y值
        rowModel.sizeHeight = cellHigh; //記錄每個cell的高度
        [_rowModeAry addObject:rowModel];  //添加到cell的數(shù)據(jù)數(shù)組中
        addUpHigh += cellHigh;    // 累積得到所有cell加在一起的高度,即contentSize
    }
    //重新設(shè)置tableView的contentSize
    [self setContentSize:CGSizeMake(self.frame.size.width, addUpHigh)];
}

setNeedsLayout方法并不是同步的,它不會在本次的事件循環(huán)中進行操作,它會在下次的事件循環(huán)中進行操作,所以當你剛剛調(diào)用了tableView的reloadData方法后,去獲取tableView的contentSize之類的會往往不準確,從而造成一些BUG。

setNeedsLayout中l(wèi)ayoutSubviews中進行的操作
- (void)layoutSubviews{
    [super layoutSubviews];
    //contentOffset的y值小于0就取0,從0開始添加cell
    float startY = (self.contentOffset.y < 0)?0:self.contentOffset.y;
    // (startY + self.frame.size.height)>self.contentSize.height 即內(nèi)容沒有超過屏幕,所以取內(nèi)容的高
    float endY = (startY + self.frame.size.height)>self.contentSize.height?self.contentSize.height:startY + self.frame.size.height;
    
    RowInfoModel *rowStartModel = [RowInfoModel new];
    rowStartModel.originY = startY;
    
    RowInfoModel *rowEndModel = [RowInfoModel new];
    rowEndModel.originY = endY;
    //二分查找法找出可視區(qū)域內(nèi)的第一個和最后一個cell
    NSInteger startIndex =  [self binarySearchFromAry:_rowModeAry object:rowStartModel];
    NSInteger endIndex   = [self binarySearchFromAry:_rowModeAry object:rowEndModel];
    
    //NSRange visibleCellRange = [self visibleRowRange];
    NSRange visibleCellRange = NSMakeRange(startIndex, endIndex-startIndex+1);
    
    // 系統(tǒng)的二分查找法
    startIndex = [_rowModeAry indexOfObject:rowStartModel inSortedRange:NSMakeRange(0, _rowModeAry.count-1) options:NSBinarySearchingInsertionIndex usingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        RowInfoModel *rowStartModel = obj1;
        RowInfoModel *rowEndModel = obj2;
        
        if (rowStartModel.originY > rowEndModel.originY &&rowStartModel.originY > rowEndModel.originY + rowEndModel.sizeHeight) {
            return NSOrderedSame;
        }else if (rowStartModel.originY < rowEndModel.originY) {
            return NSOrderedDescending;
        }else{
            return NSOrderedAscending;
        }
    }];
    
   // NSLog(@"%@--%@", NSStringFromRange(visibleCellRange), NSStringFromRange(binaryVisibleCellRange));
    
    //加載cell界面
    /*
     1,不在可視界面的cell,remove -->  重用池
     2. 加載即將出現(xiàn)的cell。  首先判斷重用池里面是否有cell,有就重用,沒有就申請新的
        重用池(保存不在界面上的)   現(xiàn)有池(保存在界面上的cell)
     */
    
    //visibleCellRange.location 即是startIndex   visibleCellRange.location + visibleCellRange.length 即是endIndex
    for (NSInteger i = visibleCellRange.location; i < visibleCellRange.location + visibleCellRange.length; i++) {
        
        //如果cell已經(jīng)在可視界面中,就不需要再重新加載了
        EOCTableViewCell *cell = _visibleCellPoolDict[@(i)];
        if (!cell) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
            cell = [_eocDelegate eocTableView:self cellForRowAtIndexPath:indexPath];
            //保存到現(xiàn)有池中
            [_visibleCellPoolDict setObject:cell forKey:@(i)];
            //從復(fù)用池中移除,一定要先保存在現(xiàn)有池中再remove,不然很有可能就被釋放掉了
            [_reuseCellPoolArr removeObject:cell];
        }
        
        RowInfoModel *rowInfoM = _rowModeAry[i];
        cell.frame = CGRectMake(0, rowInfoM.originY, self.frame.size.width, rowInfoM.sizeHeight);
        
        //cell的父視圖還不存在,即cell還沒有被添加,這時就應(yīng)該添加cell
        if (![cell superview]) {
            [self addSubview:cell];
        }
    }
    
    // 移除不在可見試圖上的cell
    NSArray *vCellKeyAry = [_visibleCellPoolDict allKeys];
    for (int i = 0; i < vCellKeyAry.count; i++) {
        // 哪些不在界面上移除到 重用池
        NSInteger indexKey = [vCellKeyAry[i] integerValue];
        if (indexKey < visibleCellRange.location || indexKey > visibleCellRange.location + visibleCellRange.length) {  //不在可見cell范圍內(nèi)
            
            //添加到復(fù)用池
            [_reuseCellPoolArr addObject:_visibleCellPoolDict[@(indexKey)]];
            //從現(xiàn)有池中移走
            [_visibleCellPoolDict removeObjectForKey:@(indexKey)];
        }
    }
}
dequeueReusableCellWithIdentifier方法的內(nèi)部實現(xiàn)
- (EOCTableViewCell *)dequeueReusableCellWithIdentifier:(NSString*)identifier{
    //遍歷復(fù)用池數(shù)組,查找是否有identifier相關(guān)的cell
    for (int i = 0; i < _reuseCellPoolArr.count; i++) {
        EOCTableViewCell *cell = _reuseCellPoolArr[i];
        if ([cell.identifier isEqual:identifier]) {
            return cell;
        }
    }
    return nil;
}

即是在復(fù)用池中查找有相同identifier的cell,如果有就取出來重用,沒有就返回nil,下一步進行創(chuàng)建

二分查找算當前可見區(qū)域的cell(第一個和最后一個cell是多少)

二分法需要一個有序的數(shù)組,而且查找的特別快,適合數(shù)量比較大的查找

/*
 二分算法  數(shù)組有序
  3
 
 1 2 3 4 5 6  7 8 9 10 11 12
 123   456
 
 1 - 1024    1024 = 2*8
 1-512  512-1024
 
 1-256  256-512
 
 */

/* a[1024]   x = 0  y = 1024  v = 123   a是長度為1024的數(shù)組,x是最小值,y是最大值,v是中間值*/
// C語言的寫法
int binarySearch(int * a, int min, int max, int v)//半開區(qū)間[x,y)
{
    int mid;
    while(min < max)
    {
        mid = min + (max-min)/2; // mid = 512
        if(v == a[mid]) {
            return  mid;//找到了
        }
        else if(v < a[mid]){
            max = mid;//在左邊
        }
        else {
            min = mid+1;//在右邊
        }
    }
    return -1;
}
//OC的寫法
- (NSInteger)binarySearchFromAry:(NSArray*)arry object:(RowInfoModel*)targetModel{
    NSInteger min = 0;
    NSInteger max = arry.count -1;
    NSInteger mid;
    while (min < max) {
        mid = min + (max - min)/2;
        RowInfoModel *midModel = arry[mid];
        if (targetModel.originY >= midModel.originY && targetModel.originY < midModel.originY + midModel.sizeHeight) {
            return mid;
        }else if(targetModel.originY < midModel.originY){  // 在左邊
            max = mid;
            if (max - min == 1) {  // 在左邊的時候,只剩兩個的時候,返回小的那個
                return min;
            }
        }else{                                            // 在右邊
            min = mid;
            if (max - min == 1) {  // 在右邊的時候,只剩兩個的時候,返回大的那個
                return max;
            }
        }
    }
    return -1;
}

最后附上整個tableView的.m文件

#import "EOCTableView.h"
#import "EOCTableViewCell.h"
#import "RowInfoModel.h"

@implementation EOCTableView{
    
}

- (instancetype)initWithFrame:(CGRect)frame{
    
    self = [super initWithFrame:frame];
    if (self) {
        _rowModeAry = [NSMutableArray array];
        _reuseCellPoolArr = [NSMutableArray array];
        _visibleCellPoolDict = [NSMutableDictionary dictionary];
    }
    return self;
}
/*
 先配置好數(shù)據(jù) 
 再配置試圖
 
 先計算所有cell高度 
 再計算每個cell位置Y
 
 */

- (void)layoutSubviews{
    [super layoutSubviews];
    //contentOffset的y值小于0就取0,從0開始添加cell
    float startY = (self.contentOffset.y < 0)?0:self.contentOffset.y;
    // (startY + self.frame.size.height)>self.contentSize.height 即內(nèi)容沒有超過屏幕,所以取內(nèi)容的高
    float endY = (startY + self.frame.size.height)>self.contentSize.height?self.contentSize.height:startY + self.frame.size.height;
    
    RowInfoModel *rowStartModel = [RowInfoModel new];
    rowStartModel.originY = startY;
    
    RowInfoModel *rowEndModel = [RowInfoModel new];
    rowEndModel.originY = endY;
    //二分查找法找出可視區(qū)域內(nèi)的第一個和最后一個cell
    NSInteger startIndex =  [self binarySearchFromAry:_rowModeAry object:rowStartModel];
    NSInteger endIndex   = [self binarySearchFromAry:_rowModeAry object:rowEndModel];
    
    //NSRange visibleCellRange = [self visibleRowRange];
    NSRange visibleCellRange = NSMakeRange(startIndex, endIndex-startIndex+1);
    
    // 系統(tǒng)的二分查找法
    startIndex = [_rowModeAry indexOfObject:rowStartModel inSortedRange:NSMakeRange(0, _rowModeAry.count-1) options:NSBinarySearchingInsertionIndex usingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        RowInfoModel *rowStartModel = obj1;
        RowInfoModel *rowEndModel = obj2;
        
        if (rowStartModel.originY > rowEndModel.originY &&rowStartModel.originY > rowEndModel.originY + rowEndModel.sizeHeight) {
            return NSOrderedSame;
        }else if (rowStartModel.originY < rowEndModel.originY) {
            return NSOrderedDescending;
        }else{
            return NSOrderedAscending;
        }
    }];
    
   // NSLog(@"%@--%@", NSStringFromRange(visibleCellRange), NSStringFromRange(binaryVisibleCellRange));
    
    //加載cell界面
    /*
     1,不在可視界面的cell,remove -->  重用池
     2. 加載即將出現(xiàn)的cell。  首先判斷重用池里面是否有cell,有就重用,沒有就申請新的
        重用池(保存不在界面上的)   現(xiàn)有池(保存在界面上的cell)
     */
    
    //visibleCellRange.location 即是startIndex   visibleCellRange.location + visibleCellRange.length 即是endIndex
    for (NSInteger i = visibleCellRange.location; i < visibleCellRange.location + visibleCellRange.length; i++) {
        
        //如果cell已經(jīng)在可視界面中,就不需要再重新加載了
        EOCTableViewCell *cell = _visibleCellPoolDict[@(i)];
        if (!cell) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
            cell = [_eocDelegate eocTableView:self cellForRowAtIndexPath:indexPath];
            //保存到現(xiàn)有池中
            [_visibleCellPoolDict setObject:cell forKey:@(i)];
            //從復(fù)用池中移除,一定要先保存在現(xiàn)有池中再remove,不然很有可能就被釋放掉了
            [_reuseCellPoolArr removeObject:cell];
        }
        
        RowInfoModel *rowInfoM = _rowModeAry[i];
        cell.frame = CGRectMake(0, rowInfoM.originY, self.frame.size.width, rowInfoM.sizeHeight);
        
        //cell的父視圖還不存在,即cell還沒有被添加,這時就應(yīng)該添加cell
        if (![cell superview]) {
            [self addSubview:cell];
        }
    }
    
    // 移除不在可見試圖上的cell
    NSArray *vCellKeyAry = [_visibleCellPoolDict allKeys];
    for (int i = 0; i < vCellKeyAry.count; i++) {
        // 哪些不在界面上移除到 重用池
        NSInteger indexKey = [vCellKeyAry[i] integerValue];
        if (indexKey < visibleCellRange.location || indexKey > visibleCellRange.location + visibleCellRange.length) {  //不在可見cell范圍內(nèi)
            
            //添加到復(fù)用池
            [_reuseCellPoolArr addObject:_visibleCellPoolDict[@(indexKey)]];
            //從現(xiàn)有池中移走
            [_visibleCellPoolDict removeObjectForKey:@(indexKey)];
        }
    }
}

- (EOCTableViewCell *)dequeueReusableCellWithIdentifier:(NSString*)identifier{
    //遍歷復(fù)用池數(shù)組,查找是否有identifier相關(guān)的cell
    for (int i = 0; i < _reuseCellPoolArr.count; i++) {
        EOCTableViewCell *cell = _reuseCellPoolArr[i];
        if ([cell.identifier isEqual:identifier]) {
            return cell;
        }
    }
    return nil;
}

/* section = 0*/

- (void)reloadData{
    
    [self countRowPosition];// 數(shù)據(jù)準備
    [self setNeedsLayout];// setNeedsLayout不會在當前事件循環(huán)操作,會放在下一個事件循環(huán)
}

// 保存postion數(shù)據(jù)
- (void)countRowPosition{
    // 先清空原數(shù)據(jù),避免疊加
    [_rowModeAry removeAllObjects];
    float addUpHigh = 0;
    
    for (int i = 0; i < [_eocDelegate eocTableView:self numberOfRowsInSection:0]; i++) {
       
        NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0];
        //有多少cell就要先走多少遍heightForRowAtIndexPath這個方法
        float cellHigh = [_eocDelegate eocTableView:self heightForRowAtIndexPath:path];
        RowInfoModel *rowModel = [[RowInfoModel alloc] init];
        rowModel.originY = addUpHigh;   //記錄每個cell的初始Y值
        rowModel.sizeHeight = cellHigh; //記錄每個cell的高度
        [_rowModeAry addObject:rowModel];  //添加到cell的數(shù)據(jù)數(shù)組中
        addUpHigh += cellHigh;    // 累積得到所有cell加在一起的高度,即contentSize
    }
    //重新設(shè)置tableView的contentSize
    [self setContentSize:CGSizeMake(self.frame.size.width, addUpHigh)];
}
// 計算當前可見區(qū)域的 cell
- (NSRange)visibleRowRange{
    
    //contentOffset的y值小于0就取0,從0開始添加cell
    float startY = (self.contentOffset.y < 0)?0:self.contentOffset.y;
    // (startY + self.frame.size.height)>self.contentSize.height 即內(nèi)容沒有超過屏幕,所以取內(nèi)容的高
    float endY = (startY + self.frame.size.height)>self.contentSize.height?self.contentSize.height:startY + self.frame.size.height;
    
    //開始索引
    NSInteger startIndex = -1;
    //結(jié)束索引
    NSInteger endIndex =  - 1;
    for (int i = 0; i < _rowModeAry.count; i++) {
        
        RowInfoModel *rowInfoM = _rowModeAry[i];
        if (startIndex == -1) {
            //找到可見區(qū)域內(nèi)的第一個cell
            if (startY >= rowInfoM.originY && startY < rowInfoM.originY + rowInfoM.sizeHeight) {
                startIndex = i;
            }
        }else{
            //找到可見區(qū)域內(nèi)的最后一個cell
            if (endY >= rowInfoM.originY && endY < rowInfoM.originY + rowInfoM.sizeHeight) {
                endIndex = i;
            }
        }
    }
    // 需要 +1  是因為startIndex是從0開始的
    return NSMakeRange(startIndex, endIndex-startIndex+1);
}

/*
 二分算法  數(shù)組有序
  3
 
 1 2 3 4 5 6  7 8 9 10 11 12
 123   456
 
 1 - 1024    1024 = 2*8
 1-512  512-1024
 
 1-256  256-512
 
 */

/* a[1024]   x = 0  y = 1024  v = 123  a是長度為1024的數(shù)組,x是最小值,y是最大值,v是中間值*/
int binarySearch(int * a, int min, int max, int v)//半開區(qū)間[x,y)
{
    int mid;
    while(min < max)
    {
        mid = min + (max-min)/2; // mid = 512
        if(v == a[mid]) {
            return  mid;//找到了
        }
        else if(v < a[mid]){
            max = mid;//在左邊
        }
        else {
            min = mid+1;//在右邊
        }
    }
    return -1;
}


- (NSInteger)binarySearchFromAry:(NSArray*)arry object:(RowInfoModel*)targetModel{
    
    NSInteger min = 0;
    NSInteger max = arry.count -1;
    NSInteger mid;
    while (min < max) {
        mid = min + (max - min)/2;
        RowInfoModel *midModel = arry[mid];
        if (targetModel.originY >= midModel.originY && targetModel.originY < midModel.originY + midModel.sizeHeight) {
            return mid;
        }else if(targetModel.originY < midModel.originY){  // 在左邊
            max = mid;
            if (max - min == 1) {  // 在左邊的時候,只剩兩個的時候,返回小的那個
                return min;
            }
        }else{                                            // 在右邊
            min = mid;
            if (max - min == 1) {  // 在右邊的時候,只剩兩個的時候,返回大的那個
                return max;
            }
        }
    }
    return -1;
}
@end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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