美文网首页
[iOS]探秘系列之-MJRefresh(部分疑点解惑)

[iOS]探秘系列之-MJRefresh(部分疑点解惑)

作者: 未来行者 | 来源:发表于2017-08-11 16:18 被阅读60次
    深海.jpg

    这篇文章可以作为整个探秘系列的开端,后面会有很多短小精悍的小文章来探秘各种框架中的"未解之谜",从而可以让我们加深对iOS的理解,进而站在"巨人"的肩膀上创造出属于我们自己的Cocoa Project.

    声明

    这里本人不会写长篇大论,尽量保持一篇文章只讲述一个到两个点,这样我自己更容易梳理,大家也更容易理解.下面,精彩开始,我们的"开胃菜"就是常用上下拉刷新框架---MJRefresh.

    开端

    某一天,项目经理大大,突然给我布置了一个任务:我们涉及到的所有列表页都需要下拉可以加载最新数据,上拉可以加载更多数据.我当时就心想,这有何难,MJRefresh不就是为此量身定做的?于是拿出来三下五除二,搞定!然后去向经理大大邀功说已经完成了,经理大大也很震惊,没想到我这么快做好了,技术出身的他问我是怎么做的,我很理所当然的说出了是用了三方框架,然后他又问我:你知道是怎么实现这些功能的吗?然后......我懵逼了.那种囧态恐怕一生都不想记起了.

    解密 - - mj_header 与 headerWithRefreshingBlock/headerWithRefreshingTarget的联系

    通用姿势:

    self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
        [self getListData];
        }];
    

    这里我有几个好奇的点:

    1. [MJRefreshNormalHeader headerWithRefreshingBlock]返回的是什么?
    2. mj_header是什么东东,tableView没有这个属性啊?
    3. 它俩到底是怎么关联的?如何拿到要监听的srollView?

    各种command+左键查看之后发现如下几点:

    1. MJRefreshNormalHeader ->(表示继承于) MJRefreshStateHeader -> MJRefreshHeader -> MJRefreshComponent,有这么一个继承关系,而MJRefreshComponent的父类是UIView! 而headerWithRefreshingBlock返回的是一个instancetype类型,根据instancetype的特性,就是返回的是一个UIView的对象.
    2. mj_header是通过运行时为UIScrollView动态添加的属性,它的类型是MJRefreshHeader,根据上面的继承关系,也是一个view.那么这句代码就很好解释了
    //这句代码返回了一个view,并且包含了执行刷新的方法
     [MJRefreshNormalHeader headerWithRefreshingBlock:^{
        [self getListData];
        }];
    //这句代码接收了返回的view,进一步理解就是给tableView添加了一个view,作为顶部显示刷新状态的header
    self.tableView.mj_header = headerWithRefreshingBlock 返回的view;
    
    1. 了解以上两点之后,至于两者如何关联,我也是百思不得其解,后来进入mj_header的set方法以及MJRefreshComponent中发现了原因.
    • 首先,mj_header的set方法处于UIScrollView的分类UIScrollView+MJRefresh.h中,这里有一句代码表示当前的ScrollView加入了上面2里的那个view:
    - (void)setMj_header:(MJRefreshHeader *)mj_header
    {
        if (mj_header != self.mj_header) {
           //其他代码略
    
            //就是这一句代码,其实是将header插入到了当前的ScrollView中,这句代码很重要,会触发另一个方法.
            [self insertSubview:mj_header atIndex:0];
    
            //其他代码略
        }
    }
    
    • 重点来了,因为我们的mj_header类型其实是MJRefreshHeader,而MJRefreshHeader的父类又是MJRefreshComponent,所以在MJRefreshComponent里我们发现了一个方法:
    - (void)willMoveToSuperview:(UIView *)newSuperview{
    };
    

    这个方法我认为就是整个MJRefresh的核心,正是因为这个方法的特性,才使得后面对scrollView contentOffset的监听成为可能.

    这里我们思考一个问题:MJRefresh采用的是KVO来监听scrollView的contentOffset,那么我们如何拿到这个scrollView呢?
    答案就是willMoveToSuperview:(UIView )newSuperview方法.根据官方文档:

    willMoveToSuperview

    描述的意思是:当前的view(这里是mj_header)的父类改变成指定的类(scrollView)的时候,这个方法会触发.
    参数描述中这个newSuperview就是我们要找的父类scrollView,因为上面mj_header的set方法中,已经将header添加到了scrollView中,insertSubview方法会触发willMoveToSuperview.

    附代码:

    - (void)willMoveToSuperview:(UIView *)newSuperview
    {
        [super willMoveToSuperview:newSuperview];
        
        // 如果不是UIScrollView,不做任何事情
        if (newSuperview && ![newSuperview isKindOfClass:[UIScrollView class]]) return;
        
        // 旧的父控件移除监听
        [self removeObservers];
        
        if (newSuperview) { // 新的父控件
            // 设置宽度
            self.mj_w = newSuperview.mj_w;
            // 设置位置
            self.mj_x = 0;
            
            // 记录UIScrollView
            _scrollView = (UIScrollView *)newSuperview;
            // 设置永远支持垂直弹簧效果
            _scrollView.alwaysBounceVertical = YES;
            // 记录UIScrollView最开始的contentInset
            _scrollViewOriginalInset = _scrollView.contentInset;
            
            // 添加监听
            [self addObservers];
        }
    }
    

    至此,scrollView已经被我们拿到了,接下来就是利用KVO对其属性进行监听了.

    嗯,至此,今天也该告一段落了.至于关于监听的细节,其实是通过scrollView的inset和contentOffset来进行处理的,涉及到一系列逻辑判断,这里暂时不谈,因为在源码中有较多的注释可以供大家参考.后面我们会继续探索MJRefresh的框架设计-继承的经典使用!

    相关文章

      网友评论

          本文标题:[iOS]探秘系列之-MJRefresh(部分疑点解惑)

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