半年多前,写过一篇《再谈UITableView的dequeueReusableCellWithIdentifier机制》说了重用的机制和一些视图重复覆盖的问题,现在从内存方面再说下更合理的复用机制。
一、cell复用情况的分类
如果整个列表每个cell都不相同,其实也就是复用也派不上用场的。
这种之前说过的复用机制是要么清除视图重新复用,要么专门为固定的行去复用其实都无所谓,只是不用再创建新的cell对象而已。而如果列表是两类或者三类等多种的cell类型,那么复用就会起到很明显的优化效果,这种时候谈复用才是复用的精髓,也就是说cell的复用是去用根据内容分类去复用,而不是为了哪一行去复用。
二、复用的原理
首先复用的原理就是给每个cell一个对应的标记indentifier,在消失的时候也不去销毁而是放到内存池中,当又要显示的时候,直接从内存池里面取出来,而不用去创建新的对象,从而达到节省内存的目的。内存的变化就是第一次看列表的时候内存依旧是上升的,但是当每个类型都在内存池中有一份的时候,即使再怎么滑动,内存也不会因为cell出现增加了。
三、复用的实现
以demo为例,在整个cell创建的时候,分为三种cell的展示类型,分别为图片在左边、图片在右边、没有图片,同时图片在左边的时候分为有介绍文字和没有介绍文字两种,也就是总共有四种情况。所以在demo中写了三种cell类,分别为TableViewCellImgLeft
、TableViewCellImgRight
、TableViewCellNoImg
,通过创建的model内容去填充cell内容。
model的内容分为下面几项、其中介绍有的有,有的没有
typedef NS_ENUM(NSUInteger, ItemType) {
ItemTypeLeftImg,
ItemTypeRightImg,
ItemTypeNoImg,
};
@interface ItemModel : NSObject
@property (strong,nonatomic) NSString *itemTitle; //标题
@property (strong,nonatomic) NSString *itemDetail; //介绍
@property (strong,nonatomic) NSString *price; //价格
@property (strong,nonatomic) NSString *image; //预览图
@property (strong,nonatomic) NSString *payUrl; //支付链接
@property (assign,nonatomic) ItemType itemType; //展示类型
在cell中,通过下面函数获取内容布局。使用mas_remakeConstraints
而不去使用mas_makeConstraints
在下面说
-(void)configWithModel:(ItemModel*)model
{
_model = model;
[self.itemImageview mas_remakeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.contentView);
make.width.height.mas_equalTo(102);
make.left.equalTo(self.contentView).offset(14);
}];
[self.itemTitleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_itemImageview);
make.left.equalTo(_itemImageview.mas_right).offset(10.5);
}];
[self.itemDesLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_itemTitleLabel.mas_bottom).offset(9.5);
make.left.equalTo(_itemTitleLabel);
}];
[self.itemPriceLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(self.contentView).offset(-20);
make.left.equalTo(_itemTitleLabel);
}];
[self.itemBuyButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.contentView).offset(-14.5);
make.bottom.equalTo(self.contentView).offset(-20);
make.width.mas_equalTo(80);
make.height.mas_equalTo(30);
}];
}
3.1、cell内部控件的复用
在cell中,因为cell需要复用,所以内部的视图变量也要做到可以复用,不去每次创建又要防止视图被提前删除,这样才不会造成视图覆盖的问题,对内存的使用也更合理,所以可以使用懒加载的方案,当然也可以使用viewWithTag去处理,但是综合来看,还是懒加载的代码量和逻辑比较清晰。
以model的介绍为例,因为有可能为空,所以这里在初始化和赋值的时候可以去判断下,当为空的时候隐藏掉,不为空的时候显示介绍,其他的图片和标题是一样的原理。
-(UILabel*)itemDesLabel{
if (!_itemDesLabel) {
_itemDesLabel = [[UILabel alloc] init];
[self.contentView addSubview:_itemDesLabel];
[_itemDesLabel setTextColor:UIColorFromRGB(0x666666)];
[_itemDesLabel setFont:[UIFont systemFontOfSize:14]];
}
if (_model.itemDetail) {
[_itemDesLabel setHidden:NO];
[_itemDesLabel setText:_model.itemDetail];
}
else{
[_itemDesLabel setHidden:YES];
}
[_itemDesLabel sizeToFit];
return _itemDesLabel;
}
3.2、model的类型标记
为了后面分类的去复用,所以在model的初始化的时候去设置类型
NSDictionary *dic3 = [[NSDictionary alloc] initWithObjectsAndKeys:
@"玩偶抱枕",@"itemTitle",
@"玩偶抱枕diy包邮",@"itemDetail",
@"¥100",@"price",
@"",@"payUrl",
@(ItemTypeNoImg),@"itemType",
nil];
ItemModel *itemModel3 = [[ItemModel alloc] initWithDictionary:dic3];
3.3、通过model的类型去创建对应的cell
根据model已经设置好的类型,去设置不同的复用identifier,做到没类对应自己的复用,这样cell的内容才不会因为复用出现视图的覆盖。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"ItemLeftImg";
static NSString *identifier2 = @"ItemRightImg";
static NSString *identifier3 = @"ItemNoImg";
UITableViewCell *cell;
switch (((ItemModel *)[_dataArray objectAtIndex:indexPath.row]).itemType) {
case ItemTypeLeftImg:
cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[TableViewCellImgLeft alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}
[(TableViewCellImgLeft*)cell configWithModel:[_dataArray objectAtIndex:indexPath.row]];
break;
case ItemTypeRightImg:
cell = [tableView dequeueReusableCellWithIdentifier:identifier2];
if (!cell) {
cell = [[TableViewCellImgRight alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier2];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}
[(TableViewCellImgRight*)cell configWithModel:[_dataArray objectAtIndex:indexPath.row]];
break;
case ItemTypeNoImg:
cell = [tableView dequeueReusableCellWithIdentifier:identifier3];
if (!cell) {
cell = [[TableViewCellNoImg alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier3];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}
[(TableViewCellNoImg*)cell configWithModel:[_dataArray objectAtIndex:indexPath.row]];
break;
default:
break;
}
return cell;
}
四、内存控制
4.1、使用mas_makeConstraints布局内存上升
布局的时候,最初是使用mas_makeConstraints
去布局,发现内存占用在快速滚动的时候一直上升。
通过使用Instruments测试并没有发现内存的泄露,但是内存在滚动和刷新布局的时候都会出现上升,上升曲线滚动越快,上升的越快,最初想法是考虑是不是懒加载的问题,在Instruments的列表发现了内存的消耗痕迹。
这里说明在使用masonry布局的时候,一直在创建叠加占用内存,所以需要使用mas_remakeConstraints
而不去使用mas_makeConstraints
布局,这样才会去消除之前的布局占用。调整之后在运行滚动,这时候内存才是平稳正常的。
4.2、懒加载注意事项
以标题label为例,在布局的时候
[self.itemTitleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_itemImageview);
make.left.equalTo(_itemImageview.mas_right).offset(10.5);
}];
需要注意的是只有在self.itemTitleLabel这句才会去执行懒加载的函数,而_itemTitleLabel这个只是代表的变量,并不会去使用懒加载,这样的话,在block里面布局的时候,注意要使用变量_itemImageview,尽量不要再去使用self.itemImageview,这是因为这样会调用两次懒加载,白白设置了两次一样的内容,如果是出现在多线程的话,还有可能会将_itemImageview这个变量去alloc两次,所以使用的时候要注意。
五、demo下载
github下载地址:https://github.com/DamonHu/CellDequeueReusableDemo
gitee下载地址:https://gitee.com/DamonHoo/CellDequeueReusableDemo
六、最后放上一张暴力滚动测试内存的动图
版权属于:东哥笔记 - DongGe.org
本文链接:https://dongge.org/blog/641.html
自2017年12月26日起,『转载以及大段采集进行后续编辑』须注明本文标题和链接!否则禁止所有转载和采集行为!