研究了一下MJRefresh
框架, 整体的结构大概是这样的, mj_header和mj_footer都继承自一个基类MJRefreshComponent
, 这个基类继承自UIView, mj_header和mj_footer又都有本身的子类, 子类来实现具体的方法.git
框架中还有一个UIScrollView的分类UIScrollView+MJRefresh
. 这个分类是一个比较简单的分类, 包含三个属性:mj_header, mj_footer, mj_reloadDataBlock, 和一个方法- (NSInteger)mj_totalDataCount
, 这个方法能够用来获取UITableView和UICollectionView的包含数据的条目数量.github
#1. MJRefresh学习笔记web
以下代码所示, 实现的功能是, 交换两个方法的实现, 利用runtime的原理.框架
@implementation NSObject (MJRefresh) // 交换两个实例方法的实现 + (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)); } @end
- (NSInteger)mj_totalDataCount { NSInteger totalCount = 0; if ([self isKindOfClass:[UITableView class]]) { UITableView *tableView = (UITableView *)self; for (NSInteger section = 0; section<tableView.numberOfSections; section++) { totalCount += [tableView numberOfRowsInSection:section]; } } else if ([self isKindOfClass:[UICollectionView class]]) { UICollectionView *collectionView = (UICollectionView *)self; for (NSInteger section = 0; section<collectionView.numberOfSections; section++) { totalCount += [collectionView numberOfItemsInSection:section]; } } return totalCount; }
#pragma mark - header static const char MJRefreshHeaderKey = '\0'; - (void)setMj_header:(MJRefreshHeader *)mj_header { if (mj_header != self.mj_header) { // 删除旧的,添加新的 // 当添加新的头刷新控件时候, 先移除旧的头部刷新控件 [self.mj_header removeFromSuperview]; // 插入新的头部刷新控件,, 使用insertSubview:atIndex:, 将控件插入到指定的位置, 方便后面去查找这个控件, 摆放控件的位置. [self insertSubview:mj_header atIndex:0]; // 存储新的 // 使用关联(association)来给对象动态的添加一个关联对象. willChangeValueForKey: 和 didChangeValueForKey: 是为了触发KVO, 可能在其它地方监听了这个关联的变化, 以从新布局呀, 从新刷新呀之类的工做. [self willChangeValueForKey:@"mj_header"]; // KVO objc_setAssociatedObject(self, &MJRefreshHeaderKey, mj_header, OBJC_ASSOCIATION_ASSIGN); [self didChangeValueForKey:@"mj_header"]; // KVO } } - (MJRefreshHeader *)mj_header { // association的get方法, 根据实例对象和key获取一个关联对象. return objc_getAssociatedObject(self, &MJRefreshHeaderKey); }
规则的三目运算符是 [表达式1]?[表达式2]:[表达式3], 其它语言没有测试, oc中也能够这么用: [表达式1]? : [表达式3]布局
!self.mj_reloadDataBlock ? : self.mj_reloadDataBlock(self.mj_totalDataCount);
@implementation UICollectionView (MJRefresh) // load是一个初始化方法, 在类刚刚开始初始化时候调用, 在类刚开始初始化时候调用这个方法, 交换两个方法的实现 + (void)load { [self exchangeInstanceMethod1:@selector(reloadData) method2:@selector(mj_reloadData)]; } // load时候交换了这两个方法的实现, 因此在这个方法里面能够调用本身, 看起来是调用本身, 其实调用了UITableView的reloadData方法 // 这里的逻辑是这样的, 咱们刷新tableView数据时候调用reloadData, 实际上会调用到mj_reloadData方法, 这个方法的实现调用mj_reloadData实际上会调用到系统的reloadData方法, 而后执行 UIScrollView的executeReloadDataBlock方法, 来执行一个block代码块 - (void)mj_reloadData { [self mj_reloadData]; [self executeReloadDataBlock]; } @end
MJRefresh中使用的一个宏定义: // 运行时objc_msgSend #define MJRefreshMsgSend(...) ((void (*)(void *, SEL, UIView *))objc_msgSend)(__VA_ARGS__) #define MJRefreshMsgTarget(target) (__bridge void *)(target) 使用: /** * 其中 self.refreshingTarget表示要执行的方法所在的对象,id类型; self.refreshingAction表示要执行的方法,SEL类型; self表示当前的view. * 简单来讲就是执行target的action方法. */ MJRefreshMsgSend(MJRefreshMsgTarget(self.refreshingTarget), self.refreshingAction, self)
经过scrollView的这个属性能够判断出来scrollView当前是否是正在被拖拽.学习
self.scrollView.isDragging
- (void)example31 { __unsafe_unretained UIWebView *webView = self.webView; webView.delegate = self; __unsafe_unretained UIScrollView *scrollView = self.webView.scrollView; // 添加下拉刷新控件 scrollView.mj_header= [MJRefreshNormalHeader headerWithRefreshingBlock:^{ [webView reload]; }]; // 若是是上拉刷新,就以此类推 }
/** * 描述: 状态栏样式 * * @return 返回状态栏的样式 */ - (UIStatusBarStyle)preferredStatusBarStyle { return self.statusBarStyle; } /** * 描述: 状态栏是否隐藏 * * @return 返回状态栏是否隐藏 */ - (BOOL)prefersStatusBarHidden { return self.statusBarHidden; } /** * setter方法 * * @param statusBarHidden 是否隐藏的setter方法 */ - (void)setStatusBarHidden:(BOOL)statusBarHidden { _statusBarHidden = statusBarHidden; [self setNeedsStatusBarAppearanceUpdate]; } /** * setter方法 * * @param statusBarStyle 状态栏样式的setter方法 */ - (void)setStatusBarStyle:(UIStatusBarStyle)statusBarStyle { _statusBarStyle = statusBarStyle; [self setNeedsStatusBarAppearanceUpdate]; }
上下拉刷新的实现原理测试
上下拉刷新的实现仍是比较简单的. 下拉刷新的header, 实际上, header的位置的y坐标是负值, 这就致使tableView中正常状况下是不会显示出来header的, 当下拉时候, header就会被展现出来, 当下拉到必定程度, 超过header的height时候, 再松手会立刻改变tableView的inset并从新放header的位置, 切换上面的数据, endRefreshing时候, 会从新改变tableviw的inset, 执行动画将header隐藏掉. 上拉加载的原理和这个如出一辙.动画
MJRefresh第一次阅读设计
这个上下拉刷新的框架, 目前在github上有七千多的star, 对中国区的iOS开发者来讲, 是一个很不错的框架. 阅读起来也是没有太大难度的, 可是经过阅读这个开源框架仍是学到了不少的东西.code
首先, 经过继承来封装一个框架, 可让框架业务逻辑层级分明, 更易于维护. 之前我也有了解过模板方法模式
, 简单说就是经过继承实现复用, 今天阅读完MJRefresh
框架, 我想对模板方法模式又有了新的认识和理解. 在框架的设计和封装时候, 咱们能够在基础的父类中实现最基本的业务逻辑, 定义抽象类, 而后让子类去处理方法的实现. 若是方法的实现很是的复杂, 能够利用相似MJRefresh中, 处于不一样层次的子类实现本身不一样的业务, 而后再去调用父类的方法, 这样达到具体业务处理和逻辑处理的分离, 让代码更加便于维护和阅读.
其次, 封装一个框架时候注意事项. 我的认为: 1.尽量的下降程序每一个部分的耦合度. 2.要写易于扩展和维护的代码. 3.无论程序内部实现多么复杂, 抛出来的接口必定要尽量的简单.
其次阅读优秀框架, 可以很好的提高我的的水平, 也能提高程序猿们重造轮子的能力, 是不 ?