已開源空厌,可以直接使用cocoapods添加钝诚,
Podfile
里面加一條pod 'LazyScroll'
即可颖御。Demo也在Github倉庫中,LazyScrollView Demo的詳細說明凝颇。
1潘拱、LazyScroll是什么
LazyScrollView 繼承自ScrollView,目標是解決異構(gòu)(與TableView的同構(gòu)對比)滾動視圖的復(fù)用回收問題拧略。它可以支持跨View層的復(fù)用芦岂,用易用方式來生成一個高性能的滾動視圖。此方案最先在天貓iOS客戶端的首頁落地垫蛆。
2禽最、為什么要用LazyScrollView
貓客首頁之前首頁的View比較少腺怯,不需要復(fù)用和回收也有很優(yōu)秀的性能,但是之后首頁的View數(shù)量逐漸膨脹川无,沒有一套復(fù)用回收機制的ScrollView已經(jīng)影響到性能了呛占,迫切需要處理對ScrollView中View的復(fù)用和回收。
使用TableView只能用來解決同類Cell的展示懦趋,而在貓客首頁這個ScrollView里面晾虑,View的種類太多了。不適合我們的場景愕够。
而UICollectionView本身的布局和復(fù)用回收機制不夠靈活走贪,用起來也較為繁瑣佛猛。并且本來貓客的首頁就有一套相對成熟的卡片布局方案惑芭。所以誕生了LazyScrollView去解決這個問題。
3继找、LazyScroll如何用
LazyScrollView的使用和TableView很像遂跟,不過多了一個需要實現(xiàn)的方法:返回對應(yīng)index的View 相對LazyScrollView的絕對坐標。
- 實現(xiàn)LazyScrollViewDatasource
類似TableView的用法婴渡,我們需要使用方實現(xiàn)LazyScrollViewDatasource這個Delegate
@protocol TMMuiLazyScrollViewDataSource <NSObject>
@required
//ScrollView一共展示多少個item
- (NSUInteger)numberOfItemInScrollView:(TMMuiLazyScrollView *)scrollView;
//要求根據(jù)index直接返回RectModel
- (TMMuiRectModel *)scrollView:(TMMuiLazyScrollView *)scrollView rectModelAtIndex:(NSUInteger)index;
//返回下標所對應(yīng)的view
- (UIView *)scrollView:(TMMuiLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID;
@end
LazyScrollView的核心是在初始狀態(tài)就得知所有View應(yīng)該顯示的位置幻锁。這個Protocol可以讓LazyScrollView獲取到這些信息。
第一個方法很簡單边臼,獲取LazyScrollView中item的個數(shù)哄尔。
第二個方法需要按照Index返回 TMMuiRectModel
,它會攜帶對應(yīng)index的View 相對LazyScrollView的絕對坐標柠并。TMMuiRectModel是這么個東西:
@interface TMMuiRectModel:NSObject
//轉(zhuǎn)換后的絕對值rect
@property (nonatomic,assign) CGRect absRect;
//業(yè)務(wù)下標
@property (nonatomic,copy) NSString *muiID;
@end
absRect
是LazyScroll中的View相對LazyScrollView的絕對坐標岭接,muiID
是這個View在LazyScrollView中唯一的標識符,可賦值也可不賦值臼予,不賦值的話LazyScroll會處理成轉(zhuǎn)換為字符串的下標鸣戴。如果這個標識符在Protocol的第三個方法中會用到。
第三個方法粘拾,返回View窄锅。首先,我們在UIView之外加了一個Category:
@interface UIView(TMMui)
//索引過的標識缰雇,在LazyScrollView范圍內(nèi)唯一
@property (nonatomic, copy) NSString *muiID;
//重用的ID
@property (nonatomic, copy) NSString *reuseIdentifier;
這個category可以讓View攜帶muiID和reuseIdentifier,對于返回的View來說入偷,只需要在乎對View的reuseIdentifier賦值,muiID的賦值會在lazyScrollView中處理掉械哟。reuseIdentifier相同的View會被復(fù)用疏之,如果這個View的reuseIdentifier是nil或者空字符串,則不會被復(fù)用戒良。
- 調(diào)用核心API
- (void)reloadData;
重新走一遍DataSource的這些方法体捏,等同于TableView中的reloadData
- (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier
根據(jù)identifier獲取可以復(fù)用的View。和TableView的
dequeueReusableCellWithIdentifier:(NSString *)identifier
方法意義相同。通常是在 LazyScrollViewDatasource
第三個方法几缭,返回View的時候使用河泳。先嘗試獲取復(fù)用池中的View,如果沒有再去新建年栓。
4拆挥、LazyScrollView的內(nèi)部實現(xiàn)
這是一個Demo, 被復(fù)用的View,標記的backgroundColor會和之前生成的時候有所不同某抓。
STEP 1 根據(jù)DataSource獲取所有的TMMuiRectModel
根據(jù)DataSource的Delegate纸兔,拿到所有的View應(yīng)該被顯示的位置。這一步否副,核心是拿到的位置是確定的汉矿。
根據(jù)Demo,我們觀察從 0/1 - 2/3 之間這些View
這個時候LazyScrollView拿到的Rect如下:
STEP 2 排序
拿到了這些位置之后备禀,接下來做的事情就是排序洲拇。排序生成的索引會有兩個:根據(jù)頂邊(y)升序排序的索引和根據(jù)底邊(y+height)降序排序的索引。
根據(jù)頂邊(y)升序排序的索引:
根據(jù)底邊(y+height)降序排序的索引:
STEP 3 查找
前兩步是在執(zhí)行完reload曲尸,在視圖還沒有生成的時候就開始做了赋续,而接下來的步驟在要生成視圖(初始化或滾動的時候)才會去做。
我們設(shè)定了Buffer為上下各20另患,滾動超過20個像素后才會指定查找視圖并顯示的動作纽乱。
接下來就是找哪些View應(yīng)該被顯示了。舉個例子昆箕,如下圖鸦列,紅圈是應(yīng)該顯示的區(qū)域。
現(xiàn)在已知的是紅圈頂邊y是242为严,底邊y是949敛熬,加上緩沖區(qū)Buffer,應(yīng)該是找222 - 969 之間的View第股。我們要做的是应民,找到頂邊y小于969的Model和底邊y+height大于222的Model,取交集夕吻,就是我們要顯示的View
采用的方法為二分查找诲锹,在根據(jù)頂邊升序排序的索引中找969,找到的index為0(MUIID為2/2)涉馅,我們使用一個Set归园,把根據(jù)頂邊排序中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稚矿。
根據(jù)底邊排序的索引中找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钠四。
STEP 4 回收盗扒、復(fù)用、生成
我們知道了應(yīng)該顯示哪些View缀去,但是我們之后做的第一步是把不需要顯示的View加入到復(fù)用池中侣灶。
LazyScroll可以取到當前顯示了的View,拿當前顯示的View的muiID和將要顯示view的Model的muiID做對比缕碎,可以知道當前顯示的View哪些應(yīng)該被回收褥影。
LazyScrollView中有一個Dictionary,key是reuseIdentifier,Value是對應(yīng)reuseIdentifier被回收的View阎曹,當LazyScrollView得知這個View不該再出現(xiàn)了伪阶,會把View放在這里,并且把這個View hidden掉处嫌。
接下來,LazyScrollView會去調(diào)用datasource的 - (UIView *)scrollView:(TMMuiLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID;
復(fù)用還是不復(fù)用斟湃,是由datasource決定的熏迹。如果要復(fù)用,需要datasource方法內(nèi)調(diào)用 - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier
獲取復(fù)用的View凝赛,這個方法取出來的View就是在上一段所說的Dictionary中拿的注暗。
這樣,我們就完成了一次完整的循環(huán) : 找到所有View將要顯示的位置 – 排序 – 查找應(yīng)該顯示的View – 回收 – 創(chuàng)建/復(fù)用墓猎。
5捆昏、最后
LazyScroll的復(fù)用和回收能力是比較強大的,在貓客首頁使用之后毙沾,因為View數(shù)量而導(dǎo)致內(nèi)存過多的問題得到了解決骗卜。
在這套復(fù)用和回收機制的加持之下,我們將LazyScrollView繼續(xù)延伸左胞,構(gòu)造出一套完整的布局解決方案 Tangram
寇仓,它將Native中View的布局方式變得更動態(tài)化,敬請期待烤宙。
轉(zhuǎn)載出處:蘋果核 - iOS 高性能異構(gòu)滾動視圖構(gòu)建方案 —— LazyScrollView