你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> iOS開發:UITableView的底層實現

iOS開發:UITableView的底層實現

編輯:IOS開發基礎

平時比較忙,十一閒下來終於有時間寫點東西,這篇文章記錄對tableView的一些思考。提到tableView相信大家都非常熟悉,它是我們開發中最常見的控件之一,繼承自scrollView( UIScrollView的底層實現看這裡 )。它是個非常神奇的控件,仿佛有無窮無盡的子控件,在它之上可以顯示成千上萬行cell,卻不會導致內存飙升,界面卡頓。但如果tableView真的創建了成千上萬個cell,就可能導致各種問題。它是如何做到盛放成千上萬的子控件而不卡頓、內存不爆表?

相信大家都知道它的核心在於使用了重用機制,但是它是如何實現的?相信大部分人還是理解的不太清楚、不夠深刻。下面我將帶大家一起實現一個簡易的tableView,重點放在 重用機制 的實現。讀完這篇文章相信大家能對tableView有一個更加深刻的認識。

cell 的重用

cell的重用,使用享元模式。下邊帶領大家一步步實現重用機制,由於本人能力有限,我盡量用簡短的語言寫的通俗易懂,如果您覺得寫的不好,也請不要噴我。

首先tableView肯定繼承自UIScrollView,在UIScrollView滑動的時候我們需要不停的檢查是否有新的cell進入界面需要顯示,舊的cell離開界面需要移除。這一步我們可以通過重寫layoutSubviews或者setContentOffset方法來實現,然後在此方法中首先我們需要計算當下要顯示第幾行到第幾行的cell,然後拿到需要顯示的cell放在界面,最後移除離開屏幕的cell。下面我們來一步一步實現。

  • 計算需要顯示第幾行:一個全局的數組中存放的是一個個存儲cell信息的對象,這些對象中包括cell開始位置、高度、以及所屬的indexPath。我們能通過遍歷或者二分查找快速找到當下需要顯示的cell的開始行和結束行。二分查找的時間復雜度是:O()=O(logn),10000次查找最多也只需要14次,所以我們采用二分查找,因為在Foundation框架中有對二分查找的封裝,我們直接采用就行,當然也可以自己實現。代碼如下:

// 計算將要顯示的是第幾行到第幾行
- (NSRange)numberOfRowsWillShowInPGLTableView:(CGFloat)start end:(CGFloat)end {
    PGLRowDetail *startDetail = [[PGLRowDetail alloc] init];
    startDetail.startY = start;
    PGLRowDetail *endDetail = [[PGLRowDetail alloc] init];
    endDetail.startY = end;

    NSInteger startIndex = [self.rowRecords indexOfObject:startDetail inSortedRange:NSMakeRange(0, self.rowRecords.count) options:NSBinarySearchingInsertionIndex usingComparator:^NSComparisonResult(PGLRowDetail * obj1, PGLRowDetail * obj2) {
        if (obj1.startY < obj2.startY) return NSOrderedAscending;
        return NSOrderedDescending;
    }];
    if (startIndex > 0) startIndex--;

    NSInteger endIndex = [self.rowRecords indexOfObject:endDetail inSortedRange:NSMakeRange(0, self.rowRecords.count - 1) options:NSBinarySearchingInsertionIndex usingComparator:^NSComparisonResult(PGLRowDetail * obj1, PGLRowDetail * obj2) {
        if (obj1.startY < obj2.startY) return NSOrderedAscending;
        return NSOrderedDescending;
    }];
    if (endIndex > 0) endIndex--;

    return NSMakeRange(startIndex, endIndex - startIndex + 1);
}
  • 判斷要顯示的cell是否已經在界面上,如不在從cellForRow方法中獲取cell,cellForRow首先會從重用池中查找對應標識符的cell,如果找到從緩存池中移除,如果找不到重新創建,然後添加在界面上,代碼如下:

// 放置需要顯示的cell
    for (NSUInteger i = range.location; i < range.location + range.length; i++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
        PGLTableViewCell *cell = [self.visibleCells objectForKey:@(i)];
        if (cell == nil) {
            cell = [self.dataSource pgtableView:self cellForRowAtIndexPath:indexPath];
            [self.visibleCells setObject:cell forKey:@(i)];
            PGLRowDetail *detail = self.rowRecords[i];
            cell.frame = CGRectMake(0, detail.startY, self.frame.size.width, detail.rowHeight);
            [self addSubview:cell];
        }

    }
    // 從重用池中獲取cell
    - (PGLTableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier {
    PGLTableViewCell *reuseCell = nil;
    for (PGLTableViewCell *cell in self.reusePool) {
        if ([cell.reuseIdentifier isEqualToString:identifier]) {
            reuseCell = cell;
            break;
        }
    }
    if (reuseCell) {
        [self.reusePool removeObject:reuseCell];
    }
    return reuseCell;
}
  • 判斷cell是否已經離開屏幕,如果離開就從屏幕上移除,加入重用池。代碼如下:

// 移除離開屏幕的cell,同時放入重用池
    NSArray *allVisibleCells = [self.visibleCells allKeys];
    for (NSNumber *numb in allVisibleCells) {
        if (!NSLocationInRange([numb integerValue], range)) {
            PGLTableViewCell *cell = [self.visibleCells objectForKey:numb];
            [self.reusePool addObject:cell];
            [self.visibleCells removeObjectForKey:numb];
            [cell removeFromSuperview];
        }
    }

以上就是重用機制的實現,如果不懂可以在這裡看詳細代碼。

總結:當然tableView有許多強大的功能,我們只是演示了一個簡單的重用機制,比如各種代理以及數據源方法,有時間我會盡量補充,如果感興趣你可以嘗試去實現它,我相信對你來說應該是個小問題。



文章轉自 PaulLi哥的簡書
原文鏈接:http://www.jianshu.com/p/6abec6dec19f
著作權歸作者所有,轉載請聯系作者獲得授權,並標注“簡書作者”。


  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved