MJRefresh 源码阅读

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来创造我们自己需要的下拉刷新视图,扩展性强。

你可能感兴趣的:(MJRefresh 源码阅读)