LazyScrollView源碼閱讀筆記

LazyScrollView是一個類似TableView的高性能滾動視圖,他的作者在開源的同時,提供了詳細的內(nèi)容介紹,如下:

LazyScrollView中文說明
LazyScrollView中文Demo說明

我的筆記只是我的一些補充內(nèi)容與思考,以下是我的學(xué)習(xí)筆記

--

結(jié)構(gòu):非常簡單&易懂

TMMuiLazyScrollView.png

--

當(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)一個類似這樣的高性能視圖,這份源碼可能是一個不錯的選擇~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市陌选,隨后出現(xiàn)的幾起案子理郑,更是在濱河造成了極大的恐慌,老刑警劉巖咨油,帶你破解...
    沈念sama閱讀 212,686評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件您炉,死亡現(xiàn)場離奇詭異,居然都是意外死亡臼勉,警方通過查閱死者的電腦和手機邻吭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宴霸,“玉大人囱晴,你說我怎么就攤上這事∑靶唬” “怎么了畸写?”我有些...
    開封第一講書人閱讀 158,160評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長氓扛。 經(jīng)常有香客問我枯芬,道長,這世上最難降的妖魔是什么采郎? 我笑而不...
    開封第一講書人閱讀 56,736評論 1 284
  • 正文 為了忘掉前任千所,我火速辦了婚禮,結(jié)果婚禮上蒜埋,老公的妹妹穿的比我還像新娘淫痰。我一直安慰自己,他們只是感情好整份,可當(dāng)我...
    茶點故事閱讀 65,847評論 6 386
  • 文/花漫 我一把揭開白布待错。 她就那樣靜靜地躺著,像睡著了一般烈评。 火紅的嫁衣襯著肌膚如雪火俄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,043評論 1 291
  • 那天讲冠,我揣著相機與錄音瓜客,去河邊找鬼。 笑死竿开,一個胖子當(dāng)著我的面吹牛谱仪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播德迹,決...
    沈念sama閱讀 39,129評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼芽卿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胳搞?” 一聲冷哼從身側(cè)響起卸例,我...
    開封第一講書人閱讀 37,872評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肌毅,沒想到半個月后筷转,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,318評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡悬而,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,645評論 2 327
  • 正文 我和宋清朗相戀三年呜舒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笨奠。...
    茶點故事閱讀 38,777評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡袭蝗,死狀恐怖唤殴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情到腥,我是刑警寧澤朵逝,帶...
    沈念sama閱讀 34,470評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站乡范,受9級特大地震影響配名,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晋辆,卻給世界環(huán)境...
    茶點故事閱讀 40,126評論 3 317
  • 文/蒙蒙 一渠脉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瓶佳,春花似錦芋膘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至贴彼,卻和暖如春潜腻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背器仗。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評論 1 267
  • 我被黑心中介騙來泰國打工吧慢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耻卡,地道東北人。 一個月前我還...
    沈念sama閱讀 46,589評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像布蔗,于是被迫代替她去往敵國和親棱烂。 傳聞我的和親對象是個殘疾皇子境输,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,687評論 2 351

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,858評論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫仪或、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,066評論 4 62
  • 五年前臂容,還是小白一枚科雳。 吹比黑學(xué),是我的大學(xué)四年脓杉。 第一年糟秘,是在吹水中度過的。嘴里吹著曾想做而沒敢做的牛球散,...
    向前飛不后悔閱讀 196評論 0 0
  • 她說,人生就是痛苦的凌净。悲觀的淚一點一滴腐蝕著一個似乎永遠樂觀的少年悲龟。 總有一些人和你經(jīng)歷一段感情,才讓你領(lǐng)會感...
    青而立閱讀 202評論 3 0
  • 想想再算算一生也不過那么十幾二十個五個年頭⌒何茫現(xiàn)在自己已經(jīng)二十出頭了躲舌,也過了人生的四個五年了丑婿。想想在前四個五年里性雄,自...
    朱提閱讀 616評論 0 0