iOS 滚动视图的复用问题解决方案

LazyScroll是什么

LazyScrollView 继承自ScrollView,目标是解决异构(与TableView的同构对比)滚动视图的复用回收问题。它能够支持跨View层的复用,用易用方式来生成一个高性能的滚动视图。web

为何要用LazyScrollView

咱们在作首页的时候,每每展现的东西会不少,随着View数量逐渐膨胀,没有一套复用回收机制的ScrollView已经影响到性能了,迫切须要处理对ScrollView中View的复用和回收。使用TableView只能用来解决同类Cell的展现,然而在实际的场景中在ScrollView里面,View的种类每每会比较多,因此使用TableView不适合咱们的场景。
而UICollectionView自己的布局和复用回收机制不够灵活,用起来也较为繁琐。因此诞生了LazyScrollView去解决这个问题。这也是天猫iOS客户端的首页落地方案。ide

LazyScroll使用

LazyScrollView的使用和TableView很像,不过多了一个须要实现的方法:返回对应index的View 相对LazyScrollView的绝对坐标。svg

实现LazyScrollViewDatasource

相似TableView的用法,咱们须要使用方实现LazyScrollViewDatasource的Delegate。布局

@protocol TMMuiLazyScrollViewDataSource <NSObject>
@required
//ScrollView展现item个数
- (NSUInteger)numberOfItemInScrollView:(TMMuiLazyScrollView *)scrollView; //要求根据index直接返回RectModel - (TMMuiRectModel *)scrollView:(TMMuiLazyScrollView *)scrollView rectModelAtIndex:(NSUInteger)index; //返回下标所对应的view - (UIView *)scrollView:(TMMuiLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID;

LazyScrollView的核心是在初始状态就得知全部View应该显示的位置。第一个方法很简单,获取LazyScrollView中item的个数。第二个方法须要按照Index返回TMMuiRectModel ,它会携带对应index的View 相对LazyScrollView的绝对坐标。
这里出现了一个TMMuiRectModel ,这是个什么东西呢?咱们看一下代码:性能

@interface TMMuiRectModel:NSObject
//转换后的绝对值rect
@property (nonatomic,assign) CGRect absRect;
//业务下标
@property (nonatomic,copy) NSString *muiID;

这里有两个属性,absRect是LazyScroll中的View相对LazyScrollView的绝对坐标,muiID是这个View在LazyScrollView中惟一的标识符,可赋值也可不赋值。
第三个方法,返回View。ui

@interface UIView(TMMui)

//索引过的标识,在LazyScrollView范围内惟一
@property (nonatomic, copy) NSString  *muiID;
//重用的ID
@property (nonatomic, copy) NSString *reuseIdentifier;

首先,咱们在UIView以外加了一个Category,这个category可让View携带muiID和reuseIdentifier,对于返回的View来讲,只须要在意对View的reuseIdentifier赋值,muiID的赋值会在lazyScrollView中处理掉。reuseIdentifier相同的View会被复用,若是这个View的reuseIdentifier是nil或者空字符串,则不会被复用。atom

LazyScrollView内部原理分析

首先来看一个简单的案例:
这里写图片描述spa

根据DataSource获取全部的TMMuiRectModel

根据DataSource的Delegate,拿到全部的View应该被显示的位置。这一步,核心是拿到的位置是肯定的。根据Demo,咱们观察从 0/1 - 2/3 之间这些View,这个时候LazyScrollView拿到的Rect以下:.net

Index 标号(MUIID) Rect
0 0/0 origin = (x = 25, y = 15), size = (width = 156, height = 150
1 0/1 origin = (x = 194, y = 15), size = (width = 156, height = 150)
2 0/2 origin = (x = 25, y = 180), size = (width = 156, height = 150)
3 0/3 origin = (x = 194, y = 180), size = (width = 156, height = 150
4 1/0 origin = (x = 5, y = 360), size = (width = 177.5, height = 150)
5 1/1 origin = (x = 192.5, y = 426), size = (width = 84, height = 84)
6 1/2 origin = (x = 192.5, y = 360), size = (width = 177.5, height = 56)
7 1/3 origin = (x = 286.5, y = 426), size = (width = 83.5, height = 84)
8 2/0 origin = (x = 25, y = 530), size = (width = 325, height = 150)
9 2/1 origin = (x = 25, y = 695), size = (width = 325, height = 150)
10 2/2 origin = (x = 25, y = 860), size = (width = 325, height = 150)

排序

拿到了这些位置以后,接下来作的事情就是排序。排序生成的索引会有两个:根据顶边(y)升序排序的索引和根据底边(y+height)降序排序的索引。
根据顶边(y)升序排序的索引3d

Index 标号(MUIID) Rect
0 0/0 origin = (x = 25, y = 15), size = (width = 156, height = 150
1 0/1 origin = (x = 194, y = 15), size = (width = 156, height = 150)
2 0/2 origin = (x = 25, y = 180), size = (width = 156, height = 150)
3 0/3 origin = (x = 194, y = 180), size = (width = 156, height = 150
4 1/0 origin = (x = 5, y = 360), size = (width = 177.5, height = 150)
5 1/1 origin = (x = 192.5, y = 360), size = (width = 177.5, height = 56)
6 1/2 origin = (x = 192.5, y = 360), size = (width = 177.5, height = 56)
7 1/3 origin = (x = 286.5, y = 426), size = (width = 83.5, height = 84)
8 2/0 origin = (x = 25, y = 530), size = (width = 325, height = 150)
9 2/1 origin = (x = 25, y = 695), size = (width = 325, height = 150)
10 2/2 origin = (x = 25, y = 860), size = (width = 325, height = 150)

根据底边(y+height)降序排序的索引

Index 标号(MUIID) Rect
0 2/2 origin = (x = 25, y = 860), size = (width = 325, height = 150)
1 2/1 origin = (x = 25, y = 695), size = (width = 325, height = 150)
2 2/0 origin = (x = 25, y = 530), size = (width = 325, height = 150)
3 1/0 origin = (x = 5, y = 360), size = (width = 177.5, height = 150)
4 1/2 origin = (x = 192.5, y = 360), size = (width = 177.5, height = 56)
5 1/3 origin = (x = 286.5, y = 426), size = (width = 83.5, height = 84)
6 1/1 origin = (x = 192.5, y = 426), size = (width = 84, height = 84)
7 0/2 origin = (x = 25, y = 180), size = (width = 156, height = 150)
8 0/3 origin = (x = 194, y = 180), size = (width = 156, height = 150
9 0/0 origin = (x = 25, y = 15), size = (width = 156, height = 150
10 0/1 origin = (x = 194, y = 15), size = (width = 156, height = 150)

查找

前两步是在执行完reload,在视图尚未生成的时候就开始作了,而接下来的步骤在要生成视图(初始化或滚动的时候)才会去作。
咱们设定了Buffer为上下各20,滚动超过20个像素后才会指定查找视图并显示的动做。举个例子,以下图,红圈是应该显示的区域。
这里写图片描述
如上图所示,如今已知的是红圈顶边y是242,底边y是949,加上缓冲区Buffer,应该是找222 - 969 之间的View。咱们要作的是,找到底边y小于969的Model和顶边y大于222的Model,取交集,就是咱们要显示的View。
采用的方法为二分查找,在根据顶边升序排序的索引中找949,找到的index为0(MUIID为2/2),咱们使用一个Set,把根据顶边排序中index >= 0 的元素先放在这里。获取的Set中包含的muiID为 0/0,0/1,0/2,0/3,1/0,1/1,1/2,1/3,2/0,2/1,2/2。
根据底边排序的索引中找222,找到的index为2,咱们把index >= 2的元素放在另外一个Set,获取的Set中包含的muiID为0/2,0/3,1/0,1/1,1/2,1/3,2/0,2/1,2/2
两个Set取交集,获得的就是咱们的ResultSet,这里面都是咱们要显示View的Model,它们的muiID是0/2,0/3,1/0,1/1,1/2,1/3,2/0,2/1,2/2。

回收、复用、生成

咱们知道了应该显示哪些View,可是咱们以后作的第一步是把不须要显示的View加入到复用池中。LazyScroll能够取到当前显示了的View,拿当前显示的View的muiID和将要显示view的Model的muiID作对比,能够知道当前显示的View哪些应该被回收。
LazyScrollView中有一个Dictionary,key是reuseIdentifier,Value是对应reuseIdentifier被回收的View,当LazyScrollView得知这个View不应再出现了,会把View放在这里,而且把这个View hidden掉。
而后,用LazyScrollView会去调用datasource。

- (UIView *)scrollView:(TMMuiLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID;

复用仍是不复用,是由datasource决定的。若是要复用,须要datasource方法内调用,即:

- (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier

获取复用的View,这个方法取出来的View就是在上一段所说的Dictionary中拿的。
最后咱们看一下LazyScrollView的使用流程:找到全部View将要显示的位置 – 排序 – 查找应该显示的View – 回收 – 建立/复用。

本文同步分享在 博客“xiangzhihong8”(CSDN)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索