MJRefresh 源码阅读

作者: DreamerZheng | 来源:发表于2015-12-11 21:25 被阅读151次

    1、Runtime
    1.1 关联对象
    该框架为UIScrollView添加了两个“成员变量”,headerfooter,这是在分类中实现的。因为是给UIScrollView及其子类UITableView和UICollectionView添加,不能通过继承实现向其添加headerfooter。所以作者采用了分类的方法。
    但是我们通常会把成员变量放在类声明的头文件里,或者放在类实现的@implementation 前面。我们不能在分类中添加成员变量,编译器会报错。Objective-C针对这一问题,提供了一种解决方案:关联对象(Associated Object)。其定义是这样的:

    /** 
     * Sets an associated value for a given object using a given key and association policy.
     * 
     * @param object The source object for the association.
     * @param key The key for the association.
     * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
     * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
     * 
     * @see objc_setAssociatedObject
     * @see objc_removeAssociatedObjects
     */
    void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    

    我们可以给对象关联很多其他对象,通过const void *key来区分,是一个唯一的指针,并且还有相应的内存管理策略,并且有对应等效的@property属性(当该关联对象成为属性时)。
    作者在此时用到的就是这个办法,

    static const char MJRefreshHeaderKey = '\0';
    - (void)setHeader:(MJRefreshHeader *)header
    {
        if (header != self.header) {
            // 删除旧的,添加新的
            [self.header removeFromSuperview];
            [self addSubview:header];
            
            // 存储新的
            [self willChangeValueForKey:@"header"]; // KVO
           //  添加关联对象
            objc_setAssociatedObject(self, &MJRefreshHeaderKey,
                                     header, OBJC_ASSOCIATION_ASSIGN);
            
            [self didChangeValueForKey:@"header"]; // KVO
        }
    }
    
    - (MJRefreshHeader *)header
    {
         //  取出关联对象
        return objc_getAssociatedObject(self, &MJRefreshHeaderKey);
    }
    
    

    1.2 self 和 super
    该框架定义子控件的Frame,调用的是- (void)layoutSubviews;,但是阅读框架的时候发现只有MJRefreshComponent 实现了这个方法:

    - (void)layoutSubviews
    {
        [super layoutSubviews];
        
        [self placeSubviews];
    }
    

    其他子类调用的就是这个方法,这就要区分一下self 和 super 的区别了。
    这个文档讲的不错 http://www.cocoachina.com/ios/20141224/10740.html
    1.3 方法交换

    //当类加载到内存的时候,调用
    + (void)load
    {
        [self exchangeInstanceMethod1:@selector(reloadData) method2:@selector(mj_reloadData)];
    }
    + (void)exchangeInstanceMethod1:(SEL)method1 method2:(SEL)method2
    {
        method_exchangeImplementations(class_getInstanceMethod(self, method1), class_getInstanceMethod(self, method2));
    }
    
    + (void)exchangeClassMethod1:(SEL)method1 method2:(SEL)method2
    {
        method_exchangeImplementations(class_getClassMethod(self, method1), class_getClassMethod(self, method2));
    }
    
    

    2 header
    该框架最基础的类是MJRefreshComponent,包含了header 和 footer 共有的属性和方法,包括刷新状态控制方法,初始化等共有方法。
    创建header的方法作者提供了两个:

    /** 创建header */
    + (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock;
    /** 创建header */
    + (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action;
    

    初始化最终是调用父类的方法:

    - (instancetype)initWithFrame:(CGRect)frame
    {
        if (self = [super initWithFrame:frame]) {
            // 准备工作
            [self prepare]; //哪个对象调用,就用该对象isa指向的类中的方法。
            
            // 默认是普通状态
            self.state = MJRefreshStateIdle;
        }
        return self;
    }
    

    之后,当header添加到父视图上时,调用了- (void)willMoveToSuperview:(UIView *)newSuperview;方法,设置一些属性,添加监听事件(KVO),监听父视图的滚动。
    其中最关键的方法是- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change;,监听contentOffset的变化,来判断header相应的state 。
    根据MJRefreshHeader这个类,继承它得到了MJRefreshStateHeader(里面包含了提示文字和最后刷新时间),MJRefreshNormalHeader(里面有活动指示器),MJRefreshGifHeader (图片),当然我们也可以继承MJRefreshHeader来创造我们自己需要的下拉刷新视图,扩展性强。

    相关文章

      网友评论

      • iOS倔强青铜:谢作者笔!补充个小点,objc_setAssociatedObject运行时方法的四个参数,源对象,关键字,关联的对象和一个关联策略。
      • KevinMK:继续下去哈~

      本文标题:MJRefresh 源码阅读

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