天天看点

iOS 高性能异构滚动视图构建方案 —— LazyScrollView

##lazyscroll是什么

lazyscrollview 继承自scrollview,目标是解决异构(与tableview的同构对比)滚动视图的复用回收问题。它可以支持跨view层的复用,用易用方式来生成一个高性能的滚动视图。此方案最先在天猫ios客户端的首页落地。

猫客首页之前首页的view比较少,不需要复用和回收也有很优秀的性能,但是之后首页的view数量逐渐膨胀,没有一套复用回收机制的scrollview已经影响到性能了,迫切需要处理对scrollview中view的复用和回收。

使用tableview只能用来解决同类cell的展示,而在猫客首页这个scrollview里面,view的种类太多了。不适合我们的场景。

而uicollectionview本身的布局和复用回收机制不够灵活,用起来也较为繁琐。并且本来猫客的首页就有一套相对成熟的卡片布局方案。所以诞生了lazyscrollview去解决这个问题。

lazyscrollview的使用和tableview很像,不过多了一个需要实现的方法:返回对应index的view 相对lazyscrollview的绝对坐标。

类似tableview的用法,我们需要使用方实现lazyscrollviewdatasource这个delegate

lazyscrollview的核心是在初始状态就得知所有view应该显示的位置。这个protocol可以让lazyscrollview获取到这些信息。

第一个方法很简单,获取lazyscrollview中item的个数。

第二个方法需要按照index返回<code>tmmuirectmodel</code> ,它会携带对应index的view 相对lazyscrollview的绝对坐标。tmmuirectmodel是这么个东西:

<code>absrect</code>是lazyscroll中的view相对lazyscrollview的绝对坐标,<code>muiid</code>是这个view在lazyscrollview中唯一的标识符,可赋值也可不赋值,不赋值的话lazyscroll会处理成转换为字符串的下标。如果这个标识符在protocol的第三个方法中会用到。

第三个方法,返回view。首先,我们在uiview之外加了一个category:

这个category可以让view携带muiid和reuseidentifier,对于返回的view来说,只需要在乎对view的reuseidentifier赋值,muiid的赋值会在lazyscrollview中处理掉。reuseidentifier相同的view会被复用,如果这个view的reuseidentifier是nil或者空字符串,则不会被复用。

<code>- (void)reloaddata;</code>

重新走一遍datasource的这些方法,等同于tableview中的reloaddata

<code>- (uiview *)dequeuereusableitemwithidentifier:(nsstring *)identifier</code>

根据identifier获取可以复用的view。和tableview的<code>dequeuereusablecellwithidentifier:(nsstring *)identifier</code>方法意义相同。通常是在<code>lazyscrollviewdatasource</code>第三个方法,返回view的时候使用。先尝试获取复用池中的view,如果没有再去新建。

这是一个demo, 被复用的view,标记的backgroundcolor会和之前生成的时候有所不同。

iOS 高性能异构滚动视图构建方案 —— LazyScrollView

根据datasource的delegate,拿到所有的view应该被显示的位置。这一步,核心是拿到的位置是确定的。

根据demo,我们观察从 0/1 - 2/3 之间这些view

这个时候lazyscrollview拿到的rect如下:

index

标号(muiid)

rect

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)升序排序的索引

rank

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

前两步是在执行完reload,在视图还没有生成的时候就开始做了,而接下来的步骤在要生成视图(初始化或滚动的时候)才会去做。

我们设定了buffer为上下各20,滚动超过20个像素后才会指定查找视图并显示的动作。

接下来就是找哪些view应该被显示了。举个例子,如下图,红圈是应该显示的区域。

iOS 高性能异构滚动视图构建方案 —— LazyScrollView

现在已知的是红圈顶边y是<b>242</b>,底边y是<b>949</b>,加上缓冲区buffer,应该是找<b>222 - 969</b> 之间的view。我们要做的是,找到<b>底边y小于969的model</b>和<b>顶边y大于222的model</b>,取交集,就是我们要显示的view

采用的方法为二分查找,在根据顶边升序排序的索引中找949,找到的index为0(muiid为2/2),我们使用一个set,把根据顶边排序中<b>index &gt;= 0</b> 的元素先放在这里。获取的set中包含的muiid为 <b>0/0,0/1,0/2,0/3,1/0,1/1,1/2,1/3,2/0,2/1,2/2</b>。

根据底边排序的索引中找222,找到的index为2,我们把<b>index &gt;= 2</b>的元素放在另一个set,获取的set中包含的muiid为<b>0/2,0/3,1/0,1/1,1/2,1/3,2/0,2/1,2/2</b>

两个set取交集,得到的就是我们的resultset,这里面都是我们要显示view的model,它们的muiid是<b>0/2,0/3,1/0,1/1,1/2,1/3,2/0,2/1,2/2</b>。

我们知道了应该显示哪些view,但是我们之后做的第一步是把不需要显示的view加入到复用池中。

lazyscroll可以取到当前显示了的view,拿当前显示的view的muiid和将要显示view的model的muiid做对比,可以知道当前显示的view哪些应该被回收。

lazyscrollview中有一个dictionary,key是reuseidentifier,value是对应reuseidentifier被回收的view,当lazyscrollview得知这个view不该再出现了,会把view放在这里,并且把这个view hidden掉。

接下来,lazyscrollview会去调用datasource的<code>- (uiview *)scrollview:(tmmuilazyscrollview *)scrollview itembymuiid:(nsstring *)muiid;</code> 复用还是不复用,是由datasource决定的。如果要复用,需要datasource方法内调用<code>- (uiview *)dequeuereusableitemwithidentifier:(nsstring *)identifier</code>获取复用的view,这个方法取出来的view就是在上一段所说的dictionary中拿的。

这样,我们就完成了一次完整的循环 : 找到所有view将要显示的位置 – 排序 – 查找应该显示的view – 回收 – 创建/复用。

##最后

lazyscroll的复用和回收能力是比较强大的,在猫客首页使用之后,因为view数量而导致内存过多的问题得到了解决。

在这套复用和回收机制的加持之下,我们将lazyscrollview继续延伸,构造出一套完整的布局解决方案<code>tangram</code>,它将native中view的布局方式变得更动态化,敬请期待。

继续阅读