美文网首页源码阅读
LazyScrollView源码阅读笔记

LazyScrollView源码阅读笔记

作者: iOS不高级工程师王老师 | 来源:发表于2017-07-11 14:59 被阅读43次

    LazyScrollView是一个类似TableView的高性能滚动视图,他的作者在开源的同时,提供了详细的内容介绍,如下:

    LazyScrollView中文说明
    LazyScrollView中文Demo说明

    我的笔记只是我的一些补充内容与思考,以下是我的学习笔记

    --

    结构:非常简单&易懂

    TMMuiLazyScrollView.png

    --

    当数据存在,所有的展示,都是从一次reload开始

    - (void)reloadData
    {
        //得到所有的item的位置,并按位置从上到下和从下到上,分别排序为2个数组
        [self creatScrollViewIndex];
        if (self.itemsFrames.count > 0) {
            
            CGRect visibleBounds = self.bounds;
            
            //根据self.bounds,得到需要复用的最大Y值和最小Y值
            CGFloat minY = CGRectGetMinY(visibleBounds) - RenderBufferWindow;
            CGFloat maxY = CGRectGetMaxY(visibleBounds) + RenderBufferWindow;
            
            //计算并展示需要展示的view,回收消失的view
            [self assembleSubviewsForReload:YES minY:minY maxY:maxY];
            
            //通过对比,计算复用view,出现的time
            //如果自己实现类似的Scrollview可以不是实现这个方法,只需要根据业务看是否刷新lastVisiblemuiID即可
            [self findViewsInVisibleRect];
        
        }
    }
    

    这里需要详细解释一下方法:

    - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY
    {
      
        //得到在Buffer下那些view在展示的区域内
        NSSet *itemShouldShowSet = [self showingItemIndexSetFrom:minY to:maxY];
        //得到在bounds下那些view在展示的区域内
        self.muiIDOfVisibleViews = [self showingItemIndexSetFrom:CGRectGetMinY(self.bounds) to:CGRectGetMaxY(self.bounds)];
    
        NSMutableSet  *recycledItems = [[NSMutableSet alloc] init];
    
        //如果之前有过一次reload,那么visibleItem会有数据,这个操作就是为了,找到那些view应该被回收,那些应该展示
        //第一次reload没有数据
        NSSet *visibles = [self.visibleItems copy];
        
        for (UIView *view in visibles)
        {
            //先确定view是否在展示区域,不在的被回收,在的加入要reload数组
            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:)])
                {
                    //如果调用了reload,或者shouldReloadItems包含了这个id,则从计算出来的visibleItems里寻找item
                    if (shouldReload) {
                        self.currentVisibleItemMuiID = muiID;
                    }
                    else {
                        /*
                        如果没有调用reload,或者shouldReloadItems不包含了这个id,则创建一个新的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数组
                    if (viewToShow)
                    {
                        viewToShow.muiID = muiID;
                        if (![self.visibleItems containsObject:viewToShow]) {
                            [self.visibleItems addObject:viewToShow];
                        }
                    }
                }
                //从应该要reload的数组里删除
                [self.shouldReloadItems removeObject:muiID];
            }
        }
    }
    

    寻找复用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(@"从复用池删除,此时复用池有 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组合成一个可变集合,放入复用池
        NSMutableSet *result = [self.recycledIdentifierItemsDic objectForKey:reuseIdentifier];
        if (result == nil) {
            result = [[NSMutableSet alloc] init];
            [self.recycledIdentifierItemsDic setObject:result forKey:reuseIdentifier];
        }
        return result;
    }
    

    为什么这里的复用池使用了一个dict,里面根据reuseIdentifier放一个集合,我这里猜想是因为天猫本身可能会有很多类的view,如果都放入一个数组里,可能会导致如下问题:

    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很大,例如接近一屏幕,可以考虑直接放入一个数组即可.

    --

    buffer的概念

    个人认为buffer的概念主要用于,优化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];
    
    }
    

    --

    一些其他细节,在工程里,它大量用了集合NSSet,而非NSArray,具体为什么可以参考下面的链接:NSArray和NSSet的区别

    我只粘贴一下精华,如下:

    NSSet , NSMutableSet类声明编程接口对象,无序的集合,在内存中存储方式是不连续的

    像NSArray,NSDictionary(都是有序的集合)类声明编程接口对象是有序集合,在内存中存储位置是连续的;

    NSSet和我们常用NSArry区别是:在搜索一个一个元素时NSSet比NSArray效率高,主要是它用到了一个算法hash(散列,也可直译为哈希);

    --

    这份源码阅读笔记相对简单,如果想更详细的了解,建议大家还是去阅读源码(源码量不多,最多一天就读完),再加上作者的文章辅助,相信会对它的原理了解的更多,如果以后大家想自己实现一个类似这样的高性能视图,这份源码可能是一个不错的选择~

    相关文章

      网友评论

        本文标题:LazyScrollView源码阅读笔记

        本文链接:https://www.haomeiwen.com/subject/lzhmhxtx.html