##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會和之前生成的時候有所不同。
根據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應該被顯示了。舉個例子,如下圖,紅圈是應該顯示的區域。
現在已知的是紅圈頂邊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 >= 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 >= 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的布局方式變得更動态化,敬請期待。