天天看點

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的布局方式變得更動态化,敬請期待。

繼續閱讀