解决问题

🎯  UITableView 行高性能低下问题

架构

编程思想

🧠 runtime

知识点和小技巧

💧 计算行高

/*
1. boundingRectWithSize:options:attributes:context:
2. sizeToFit: calls sizeThatFits: with current view bounds and changes bounds size.
3. sizeThatFits: return 'best' size to fit given size. does not actually resize view. Default is return existing view size
4. systemLayoutSizeFittingSize
*/

/*
sizeThatFits
sizeThatFits是UIView的方法,它需要传入一个CGSize参数,通常你可以传一个任意的Size,它会返回一个它认为最合适的CGSize给你。
不过如果要想把视图的内容显示完全(纵向),最好是将视图的实际宽度和最大高度CGFLOAT_MAX作为参数传递。这样返回的才是完全显示内容的Size。

*/
-(void)layoutSubviews {
    [super layoutSubviews]
    //1
    CGSize labelSize = [self.label sizeThatFits:CGSizeMake(self.label.bounds.size.width, CGFLOAT_MAX)];
    //2
    self.labelConstraintHeight.constant = labelSize.height + (2 * MARGIN);
 }

/*
boundingRectWithSize:options:attributes:context:
boundingRectWithSize:options:attributes:context:是NSString的方法。理解起来也非常简单,根据一些绘制字符的选项和字符属性(字体、字号、字体颜色)
等信息返回一个可以容纳字符串内容的CGRect。它同样也需要一个CGSize来确定绘制区域。
关键点:
size: 如果要显示全部内容。在宽度固定的情况下,这个CGSize的高度应该尽可能的大。所以CGSize参数同sizeThatFits。
options: 默认情况下这个方法不会绘制多行,如果要绘制多行字符,那么options参数必须为: NSStringDrawingUsesLineFragmentOrigin。
attributes: 字符属性信息也非常重要。如果要显示UILabel的全部内容,必须传递这个参数。以确保绘制的字体大小和UILabel的字体大小一致。
*/
- (void)layoutSubviews {
    [super layoutSubviews];
    
    CGRect textBounds = [self.label.text boundingRectWithSize:CGSizeMake(self.label.bounds.size.width, CGFLOAT_MAX) 
																				 options:NSStringDrawingUsesLineFragmentOrigin 
																				 attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:17]} context:nil];
    self.labelConstraintHeight.constant = ceil(textBounds.size.height) + (2 * MARGIN);
}

/*
systemLayoutSizeFittingSize ,它也是UIView的方法,是AutoLayout诞生后的产物。所以使用它的前提是需要展示内容的控件(这里指的就是UILabel)必须约束完美。
不然就不会起作用。而且必须要设置UILabel的preferredMaxLayoutWidth属性。这个属性非常重要,它影响着layout。如果设置了preferredMaxLayoutWidth,当内容超过约束区域,就会自动换行并且更新约束。
在良好约束的前提下,systemLayoutSizeFittingSize同样接受一个CGSize,不同的是这次不用计算了,直接使用系统提供的Fitting Size即可
const CGSize UILayoutFittingCompressedSize;
const CGSize UILayoutFittingExpandedSize;
*/
- (void)layoutSubviews {
    [super layoutSubviews];
    self.label.preferredMaxLayoutWidth = CGRectGetWidth(self.label.bounds);
    CGSize size = [self.label systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    self.labelConstraintHeight.constant = size.height + (2 * MARGIN);
}

💧 宏

CGFLOAT_IS_DOUBLE https://developer.apple.com/documentation/coregraphics/core_graphics_constants 定义了很多宏

下面参考 ‣

static void __FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(void (^callout)(void)) {
    callout();
}

///COM: __VA_ARGS__表示的传入的参数:...,...代表则是可变参数
#define FDPrimaryCall(...) do {__FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(^{__VA_ARGS__});} while(0)

💧  cache 通常都用 dictionary

@property (nonatomic, strong) NSMutableDictionary<id<NSCopying>, NSNumber *> *mutableHeightsByKeyForPortrait;
@property (nonatomic, strong) NSMutableDictionary<id<NSCopying>, NSNumber *> *mutableHeightsByKeyForLandscape;