LazyScrollView是一個類似TableView的高性能滾動視圖,他的作者在開源的同時,提供了詳細的內(nèi)容介紹,如下:
LazyScrollView中文說明
LazyScrollView中文Demo說明
我的筆記只是我的一些補充內(nèi)容與思考,以下是我的學(xué)習(xí)筆記
--
結(jié)構(gòu):非常簡單&易懂
--
當(dāng)數(shù)據(jù)存在,所有的展示,都是從一次reload開始
- (void)reloadData
{
//得到所有的item的位置,并按位置從上到下和從下到上,分別排序為2個數(shù)組
[self creatScrollViewIndex];
if (self.itemsFrames.count > 0) {
CGRect visibleBounds = self.bounds;
//根據(jù)self.bounds,得到需要復(fù)用的最大Y值和最小Y值
CGFloat minY = CGRectGetMinY(visibleBounds) - RenderBufferWindow;
CGFloat maxY = CGRectGetMaxY(visibleBounds) + RenderBufferWindow;
//計算并展示需要展示的view,回收消失的view
[self assembleSubviewsForReload:YES minY:minY maxY:maxY];
//通過對比,計算復(fù)用view,出現(xiàn)的time
//如果自己實現(xiàn)類似的Scrollview可以不是實現(xiàn)這個方法,只需要根據(jù)業(yè)務(wù)看是否刷新lastVisiblemuiID即可
[self findViewsInVisibleRect];
}
}
這里需要詳細解釋一下方法:
- (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY
{
//得到在Buffer下那些view在展示的區(qū)域內(nèi)
NSSet *itemShouldShowSet = [self showingItemIndexSetFrom:minY to:maxY];
//得到在bounds下那些view在展示的區(qū)域內(nèi)
self.muiIDOfVisibleViews = [self showingItemIndexSetFrom:CGRectGetMinY(self.bounds) to:CGRectGetMaxY(self.bounds)];
NSMutableSet *recycledItems = [[NSMutableSet alloc] init];
//如果之前有過一次reload,那么visibleItem會有數(shù)據(jù),這個操作就是為了,找到那些view應(yīng)該被回收,那些應(yīng)該展示
//第一次reload沒有數(shù)據(jù)
NSSet *visibles = [self.visibleItems copy];
for (UIView *view in visibles)
{
//先確定view是否在展示區(qū)域,不在的被回收,在的加入要reload數(shù)組
BOOL isToShow = [itemShouldShowSet containsObject:view.muiID];
if (!isToShow && view.reuseIdentifier.length > 0)
{
NSMutableSet *recycledIdentifierSet = [self recycledIdentifierSet:view.reuseIdentifier];
[recycledIdentifierSet addObject:view];
[view removeFromSuperview];
[recycledItems addObject:view];
}
else if (isReload && view.muiID) {
[self.shouldReloadItems addObject:view.muiID];
}
}
//取差集
[self.visibleItems minusSet:recycledItems];
[recycledItems removeAllObjects];
for (NSString *muiID in itemShouldShowSet)
{
BOOL shouldReload = isReload || [self.shouldReloadItems containsObject:muiID];
if(![self isCellVisible:muiID] || [self.shouldReloadItems containsObject:muiID])
{
if (self.dataSource && [self.dataSource conformsToProtocol:@protocol(TMMuiLazyScrollViewDataSource)] &&
[self.dataSource respondsToSelector:@selector(scrollView: itemByMuiID:)])
{
//如果調(diào)用了reload,或者shouldReloadItems包含了這個id,則從計算出來的visibleItems里尋找item
if (shouldReload) {
self.currentVisibleItemMuiID = muiID;
}
else {
/*
如果沒有調(diào)用reload,或者shouldReloadItems不包含了這個id,則創(chuàng)建一個新的view
在代理方法
- (UIView *)scrollView:(TMMuiLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID中
*/
self.currentVisibleItemMuiID = nil;
}
UIView *viewToShow = [self.dataSource scrollView:self itemByMuiID:muiID];
if ([viewToShow conformsToProtocol:@protocol(TMMuiLazyScrollViewCellProtocol)] &&
[viewToShow respondsToSelector:@selector(mui_afterGetView)]) {
[(UIView<TMMuiLazyScrollViewCellProtocol> *)viewToShow mui_afterGetView];
}
//如果沒有加入visibleItems,加入visibleItems數(shù)組
if (viewToShow)
{
viewToShow.muiID = muiID;
if (![self.visibleItems containsObject:viewToShow]) {
[self.visibleItems addObject:viewToShow];
}
}
}
//從應(yīng)該要reload的數(shù)組里刪除
[self.shouldReloadItems removeObject:muiID];
}
}
}
尋找復(fù)用view的邏輯
- (nullable UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier
{
UIView *view = nil;
if (self.currentVisibleItemMuiID) {
NSSet *visibles = self.visibleItems;
for (UIView *v in visibles) {
if ([v.muiID isEqualToString:self.currentVisibleItemMuiID]) {
view = v;
break;
}
}
}
if (nil == view) {
NSMutableSet *recycledIdentifierSet = [self recycledIdentifierSet:identifier];
view = [recycledIdentifierSet anyObject];
if (view)
{
//if exist reusable view , remove it from recycledSet.
[recycledIdentifierSet removeObject:view];
//NSLog(@"從復(fù)用池刪除,此時復(fù)用池有 count = %ld",recycledIdentifierSet.count);
//Then remove all gesture recognizers of it.
view.gestureRecognizers = nil;
}
}
if ([view conformsToProtocol:@protocol(TMMuiLazyScrollViewCellProtocol)] && [view respondsToSelector:@selector(mui_prepareForReuse)]) {
[(UIView<TMMuiLazyScrollViewCellProtocol> *)view mui_prepareForReuse];
}
return view;
}
- (NSMutableSet *)recycledIdentifierSet:(NSString *)reuseIdentifier;
{
if (reuseIdentifier.length == 0)
{
return nil;
}
//會把一類reuseIdentifier的view組合成一個可變集合,放入復(fù)用池
NSMutableSet *result = [self.recycledIdentifierItemsDic objectForKey:reuseIdentifier];
if (result == nil) {
result = [[NSMutableSet alloc] init];
[self.recycledIdentifierItemsDic setObject:result forKey:reuseIdentifier];
}
return result;
}
為什么這里的復(fù)用池使用了一個dict,里面根據(jù)reuseIdentifier放一個集合,我這里猜想是因為天貓本身可能會有很多類的view,如果都放入一個數(shù)組里,可能會導(dǎo)致如下問題:
A_view 10個
B_view 10個
C_view 10個
想找到A,卻要遍歷所有種類的view
for (int i = 0; i < 10+10+10 ;i++)
{
if (a){
break;
}
}
--
如果是dict,只需要取出dict,得到set就可以遍歷
NSSet *aSet = [dict objectForKey:@"xxx"];
for (int i = 0; i < aSet.count ;i++)
{
if (a){
break;
}
}
個人認為:如果是要做類別很多的,而且view的frame相對不大的滾動視圖,可以用這樣的方式,如果view的frame很大,例如接近一屏幕,可以考慮直接放入一個數(shù)組即可.
--
buffer的概念
個人認為buffer的概念主要用于,優(yōu)化scrollViewDidScroll里的計算時間,為了防止每一次scroll微小的滾動帶來的計算消耗,源碼如下
CGFloat currentY = scrollView.contentOffset.y;
CGFloat buffer = RenderBufferWindow / 2;
//如果大于buffer的值,才會進行計算
if (buffer < ABS(currentY - self.lastScrollOffset.y)) {
self.lastScrollOffset = scrollView.contentOffset;
[self assembleSubviews];
[self findViewsInVisibleRect];
}
--
一些其他細節(jié),在工程里,它大量用了集合NSSet,而非NSArray,具體為什么可以參考下面的鏈接:NSArray和NSSet的區(qū)別
我只粘貼一下精華,如下:
NSSet , NSMutableSet類聲明編程接口對象热监,無序的集合捺弦,在內(nèi)存中存儲方式是不連續(xù)的
像NSArray,NSDictionary(都是有序的集合)類聲明編程接口對象是有序集合孝扛,在內(nèi)存中存儲位置是連續(xù)的列吼;
NSSet和我們常用NSArry區(qū)別是:在搜索一個一個元素時NSSet比NSArray效率高,主要是它用到了一個算法hash(散列苦始,也可直譯為哈希)寞钥;
--
這份源碼閱讀筆記相對簡單,如果想更詳細的了解,建議大家還是去閱讀源碼(源碼量不多,最多一天就讀完),再加上作者的文章輔助,相信會對它的原理了解的更多,如果以后大家想自己實現(xiàn)一個類似這樣的高性能視圖,這份源碼可能是一個不錯的選擇~