UITableView優化那點事


forkingdog關於UITableView優化的 框架其實已經能夠應用在一般的場景,且有蠻多的知識點供我們借鑒,借此站在巨人的肩膀上來分析一把。

至於UITableView的瓶頸在哪里,我相信網上隨便一搜就能了解的大概,我這里順便提供下信息點:

高度計算

fd_heightForCellWithIdentifier:configuration方法
 1 - (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier configuration:(void (^)(id cell))configuration {
 2     if (!identifier) {
 3         return 0;
 4     }
 5  
 6     UITableViewCell *templateLayoutCell = [self fd_templateCellForReuseIdentifier:identifier];
 7  
 8     // Manually calls to ensure consistent behavior with actual cells. (that are displayed on screen)
 9     [templateLayoutCell prepareForReuse];
10  
11     // Customize and provide content for our template cell.
12     if (configuration) {
13         configuration(templateLayoutCell);
14     }
15  
16     return [self fd_systemFittingHeightForConfiguratedCell:templateLayoutCell];
17 }

這里先是通過調用fd_templateCellForReuseIdentifier:從dequeueReusableCellWithIdentifier取出之后,如果需要做一些額外的計算,比如說計算cell高度, 可以手動調用 prepareForReuse方法,以確保與實際cell(顯示在屏幕上)行為一致。接着執行configuration參數對Cell內容進行配置。最后通過調用fd_systemFittingHeightForConfiguratedCell:方法計算實際的高度並返回。

fd_systemFittingHeightForConfiguratedCell方法
 1 - (CGFloat)fd_systemFittingHeightForConfiguratedCell:(UITableViewCell *)cell {
 2     CGFloat contentViewWidth = CGRectGetWidth(self.frame);
 3  
 4     // If a cell has accessory view or system accessory type, its content view's width is smaller
 5     // than cell's by some fixed values.
 6     if (cell.accessoryView) {
 7         contentViewWidth -= 16 + CGRectGetWidth(cell.accessoryView.frame);
 8     } else {
 9         static const CGFloat systemAccessoryWidths[] = {
10             [UITableViewCellAccessoryNone] = 0,
11             [UITableViewCellAccessoryDisclosureIndicator] = 34,
12             [UITableViewCellAccessoryDetailDisclosureButton] = 68,
13             [UITableViewCellAccessoryCheckmark] = 40,
14             [UITableViewCellAccessoryDetailButton] = 48
15         };
16         contentViewWidth -= systemAccessoryWidths[cell.accessoryType];
17     }
18  
19     // If not using auto layout, you have to override "-sizeThatFits:" to provide a fitting size by yourself.
20     // This is the same height calculation passes used in iOS8 self-sizing cell's implementation.
21     //
22     // 1. Try "- systemLayoutSizeFittingSize:" first. (skip this step if 'fd_enforceFrameLayout' set to YES.)
23     // 2. Warning once if step 1 still returns 0 when using AutoLayout
24     // 3. Try "- sizeThatFits:" if step 1 returns 0
25     // 4. Use a valid height or default row height (44) if not exist one
26  
27     CGFloat fittingHeight = 0;
28  
29     if (!cell.fd_enforceFrameLayout & contentViewWidth > 0) {
30         // Add a hard width constraint to make dynamic content views (like labels) expand vertically instead
31         // of growing horizontally, in a flow-layout manner.
32         NSLayoutConstraint *widthFenceConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:contentViewWidth];
33         [cell.contentView addConstraint:widthFenceConstraint];
34  
35         // Auto layout engine does its math
36         fittingHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
37         [cell.contentView removeConstraint:widthFenceConstraint];
38  
39         [self fd_debugLog:[NSString stringWithFormat:@"calculate using system fitting size (AutoLayout) - %@", @(fittingHeight)]];
40     }
41  
42     if (fittingHeight == 0) {
43 #if DEBUG
44         // Warn if using AutoLayout but get zero height.
45         if (cell.contentView.constraints.count > 0) {
46             if (!objc_getAssociatedObject(self, _cmd)) {
47                 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.");
48                 objc_setAssociatedObject(self, _cmd, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
49             }
50         }
51 #endif
52         // Try '- sizeThatFits:' for frame layout.
53         // Note: fitting height should not include separator view.
54         fittingHeight = [cell sizeThatFits:CGSizeMake(contentViewWidth, 0)].height;
55  
56         [self fd_debugLog:[NSString stringWithFormat:@"calculate using sizeThatFits - %@", @(fittingHeight)]];
57     }
58  
59     // Still zero height after all above.
60     if (fittingHeight == 0) {
61         // Use default row height.
62         fittingHeight = 44;
63     }
64  
65     // Add 1px extra space for separator line if needed, simulating default UITableViewCell.
66     if (self.separatorStyle != UITableViewCellSeparatorStyleNone) {
67         fittingHeight += 1.0 / [UIScreen mainScreen].scale;
68     }
69  
70     return fittingHeight;
71 }

這里作者考慮到了如果Cell使用了accessory view或者使用了系統的accessory type,需要減掉相應的寬度。接着判斷如果使用了AutoLayout,則使用iOS 6提供的systemLayoutSizeFittingSize方法獲取高度。如果高度為0,則嘗試使用Frame Layout的方式,調用重寫的sizeThatFits方法進行獲取。如果還是為0,則給出默認高度並返回。

Cell重用

fd_templateCellForReuseIdentifier方法
 1 - (__kindof UITableViewCell *)fd_templateCellForReuseIdentifier:(NSString *)identifier {
 2     NSAssert(identifier.length > 0, @"Expect a valid identifier - %@", identifier);
 3  
 4     NSMutableDictionary *templateCellsByIdentifiers = objc_getAssociatedObject(self, _cmd);
 5     if (!templateCellsByIdentifiers) {
 6         templateCellsByIdentifiers = @{}.mutableCopy;
 7         objc_setAssociatedObject(self, _cmd, templateCellsByIdentifiers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 8     }
 9  
10     UITableViewCell *templateCell = templateCellsByIdentifiers[identifier];
11  
12     if (!templateCell) {
13         templateCell = [self dequeueReusableCellWithIdentifier:identifier];
14         NSAssert(templateCell != nil, @"Cell must be registered to table view for identifier - %@", identifier);
15         templateCell.fd_isTemplateLayoutCell = YES;
16         templateCell.contentView.translatesAutoresizingMaskIntoConstraints = NO;
17         templateCellsByIdentifiers[identifier] = templateCell;
18         [self fd_debugLog:[NSString stringWithFormat:@"layout cell created - %@", identifier]];
19     }
20  
21     return templateCell;
22 }

這里通過dequeueReusableCellWithIdentifier方法從隊列中獲取templateCell,並通過fd_isTemplateLayoutCell屬性標識其只用來充當模板計算,並不真正進行呈現,最后通過關聯對象的方式進行存取。

注意:這里通過dequeueReusableCellWithIdentifier進行獲取,也就意味着你必須對指定的Identifier先進行注冊,注冊可以通過以下三中方法:

總結:

UITableView優化方案其實還有很多,不同的場景選用不同的方案,實現效果達到預期,這才是我么最終的目標。我這里簡單介紹下其他的優化的細節:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM