MJRefresh 源码阅读
1、Runtime
1.1 关联对象
该框架为UIScrollView添加了两个“成员变量”,header
和footer
,这是在分类中实现的。因为是给UIScrollView及其子类UITableView和UICollectionView添加,不能通过继承实现向其添加header
和footer
。所以作者采用了分类的方法。
但是我们通常会把成员变量放在类声明的头文件里,或者放在类实现的@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来创造我们自己需要的下拉刷新视图,扩展性强。