UITableView優(yōu)化那點(diǎn)事

源自網(wǎng)絡(luò)

forkingdog關(guān)于UITableView優(yōu)化的框架其實(shí)已經(jīng)能夠應(yīng)用在一般的場(chǎng)景,且有蠻多的知識(shí)點(diǎn)供我們借鑒,借此站在巨人的肩膀上來(lái)分析一把。

至于UITableView的瓶頸在哪里,我相信網(wǎng)上隨便一搜就能了解的大概,我這里順便提供下信息點(diǎn):

//罪魁禍?zhǔn)?tableView:cellForRowAtIndexPath:
tableView:heightForRowAtIndexPath:

框架同樣根據(jù)這兩個(gè)痛點(diǎn)給出了解決方案:

高度計(jì)算

fd_heightForCellWithIdentifier:configuration方法
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier configuration:(void (^)(id cell))configuration {
    if (!identifier) {
        return 0;
    }
    
    UITableViewCell *templateLayoutCell = [self fd_templateCellForReuseIdentifier:identifier];
    
    // Manually calls to ensure consistent behavior with actual cells. (that are displayed on screen)
    [templateLayoutCell prepareForReuse];
    
    // Customize and provide content for our template cell.
    if (configuration) {
        configuration(templateLayoutCell);
    }
    
    return [self fd_systemFittingHeightForConfiguratedCell:templateLayoutCell];
}

這里先是通過(guò)調(diào)用fd_templateCellForReuseIdentifier:從dequeueReusableCellWithIdentifier取出之后,如果需要做一些額外的計(jì)算,比如說(shuō)計(jì)算cell高度, 可以手動(dòng)調(diào)用 prepareForReuse方法,以確保與實(shí)際cell(顯示在屏幕上)行為一致。接著執(zhí)行configuration參數(shù)對(duì)Cell內(nèi)容進(jìn)行配置。最后通過(guò)調(diào)用fd_systemFittingHeightForConfiguratedCell:方法計(jì)算實(shí)際的高度并返回。

fd_systemFittingHeightForConfiguratedCell方法
- (CGFloat)fd_systemFittingHeightForConfiguratedCell:(UITableViewCell *)cell {
    CGFloat contentViewWidth = CGRectGetWidth(self.frame);
    
    // If a cell has accessory view or system accessory type, its content view's width is smaller
    // than cell's by some fixed values.
    if (cell.accessoryView) {
        contentViewWidth -= 16 + CGRectGetWidth(cell.accessoryView.frame);
    } else {
        static const CGFloat systemAccessoryWidths[] = {
            [UITableViewCellAccessoryNone] = 0,
            [UITableViewCellAccessoryDisclosureIndicator] = 34,
            [UITableViewCellAccessoryDetailDisclosureButton] = 68,
            [UITableViewCellAccessoryCheckmark] = 40,
            [UITableViewCellAccessoryDetailButton] = 48
        };
        contentViewWidth -= systemAccessoryWidths[cell.accessoryType];
    }
    
    // If not using auto layout, you have to override "-sizeThatFits:" to provide a fitting size by yourself.
    // This is the same height calculation passes used in iOS8 self-sizing cell's implementation.
    //
    // 1. Try "- systemLayoutSizeFittingSize:" first. (skip this step if 'fd_enforceFrameLayout' set to YES.)
    // 2. Warning once if step 1 still returns 0 when using AutoLayout
    // 3. Try "- sizeThatFits:" if step 1 returns 0
    // 4. Use a valid height or default row height (44) if not exist one
    
    CGFloat fittingHeight = 0;
    
    if (!cell.fd_enforceFrameLayout && contentViewWidth > 0) {
        // Add a hard width constraint to make dynamic content views (like labels) expand vertically instead
        // of growing horizontally, in a flow-layout manner.
        NSLayoutConstraint *widthFenceConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:contentViewWidth];
        [cell.contentView addConstraint:widthFenceConstraint];
        
        // Auto layout engine does its math
        fittingHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
        [cell.contentView removeConstraint:widthFenceConstraint];
        
        [self fd_debugLog:[NSString stringWithFormat:@"calculate using system fitting size (AutoLayout) - %@", @(fittingHeight)]];
    }
    
    if (fittingHeight == 0) {
#if DEBUG
        // Warn if using AutoLayout but get zero height.
        if (cell.contentView.constraints.count > 0) {
            if (!objc_getAssociatedObject(self, _cmd)) {
                NSLog(@"[FDTemplateLayoutCell] Warning once only: Cannot get a proper cell height (now 0) from '- systemFittingSize:'(AutoLayout). You should check how constraints are built in cell, making it into 'self-sizing' cell.");
                objc_setAssociatedObject(self, _cmd, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            }
        }
#endif
        // Try '- sizeThatFits:' for frame layout.
        // Note: fitting height should not include separator view.
        fittingHeight = [cell sizeThatFits:CGSizeMake(contentViewWidth, 0)].height;
        
        [self fd_debugLog:[NSString stringWithFormat:@"calculate using sizeThatFits - %@", @(fittingHeight)]];
    }
    
    // Still zero height after all above.
    if (fittingHeight == 0) {
        // Use default row height.
        fittingHeight = 44;
    }
    
    // Add 1px extra space for separator line if needed, simulating default UITableViewCell.
    if (self.separatorStyle != UITableViewCellSeparatorStyleNone) {
        fittingHeight += 1.0 / [UIScreen mainScreen].scale;
    }
    
    return fittingHeight;
}

這里作者考慮到了如果Cell使用了accessory view或者使用了系統(tǒng)的accessory type,需要減掉相應(yīng)的寬度。接著判斷如果使用了AutoLayout,則使用iOS 6提供的systemLayoutSizeFittingSize方法獲取高度。如果高度為0,則嘗試使用Frame Layout的方式,調(diào)用重寫的sizeThatFits方法進(jìn)行獲取。如果還是為0,則給出默認(rèn)高度并返回。

Cell重用

fd_templateCellForReuseIdentifier方法
- (__kindof UITableViewCell *)fd_templateCellForReuseIdentifier:(NSString *)identifier {
    NSAssert(identifier.length > 0, @"Expect a valid identifier - %@", identifier);
    
    NSMutableDictionary<NSString *, UITableViewCell *> *templateCellsByIdentifiers = objc_getAssociatedObject(self, _cmd);
    if (!templateCellsByIdentifiers) {
        templateCellsByIdentifiers = @{}.mutableCopy;
        objc_setAssociatedObject(self, _cmd, templateCellsByIdentifiers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    UITableViewCell *templateCell = templateCellsByIdentifiers[identifier];
    
    if (!templateCell) {
        templateCell = [self dequeueReusableCellWithIdentifier:identifier];
        NSAssert(templateCell != nil, @"Cell must be registered to table view for identifier - %@", identifier);
        templateCell.fd_isTemplateLayoutCell = YES;
        templateCell.contentView.translatesAutoresizingMaskIntoConstraints = NO;
        templateCellsByIdentifiers[identifier] = templateCell;
        [self fd_debugLog:[NSString stringWithFormat:@"layout cell created - %@", identifier]];
    }
    
    return templateCell;
}

這里通過(guò)dequeueReusableCellWithIdentifier方法從隊(duì)列中獲取templateCell,并通過(guò)fd_isTemplateLayoutCell屬性標(biāo)識(shí)其只用來(lái)充當(dāng)模板計(jì)算,并不真正進(jìn)行呈現(xiàn),最后通過(guò)關(guān)聯(lián)對(duì)象的方式進(jìn)行存取。
注意:這里通過(guò)dequeueReusableCellWithIdentifier進(jìn)行獲取,也就意味著你必須對(duì)指定的Identifier先進(jìn)行注冊(cè),注冊(cè)可以通過(guò)以下三中方法:

1.使用storyboard中的Cell原型
2.使用registerNib:forCellReuseIdentifier:
3.使用registerClass:forCellReuseIdentifier:

到這里最重要的幾個(gè)方法已經(jīng)講完了,除此之外框架還針對(duì)獲取的高度進(jìn)行了緩存。緩存的方式分為兩種 :

1.根據(jù)IndexPath進(jìn)行緩存(fd_heightForCellWithIdentifier:cacheByIndexPath:configuration)
2.根據(jù)實(shí)體的唯一標(biāo)識(shí)符進(jìn)行緩存(fd_heightForCellWithIdentifier:cacheByKey:configuration)

總結(jié):

UITableView優(yōu)化方案其實(shí)還有很多,不同的場(chǎng)景選用不同的方案,實(shí)現(xiàn)效果達(dá)到預(yù)期,這才是我么最終的目標(biāo)。我這里簡(jiǎn)單介紹下其他的優(yōu)化的細(xì)節(jié):

1.復(fù)雜界面的時(shí)候,我們可以嘗試異步手動(dòng)進(jìn)行繪制。
2.針對(duì)超出屏幕的Cell進(jìn)行預(yù)緩存
3.存在大量圖片的時(shí)候,只針對(duì)目標(biāo)范圍內(nèi)的圖片進(jìn)行異步加載并緩存結(jié)果。
4.設(shè)置Views/Layers為不透明。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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