前言
通常我们都会在项目中使用三方库,它们能帮我们提高开发效率.但是随着时间的发展,你使用的某个三方库可能会停止维护.随着系统API升级,之前旧版本的库可能就用不了了,这时可能就需要替换成新的三方库.但是对于一个老项目而言,如果没有对之前的三方库进行适当的封装,那么想去替换一个旧的三方库可想而知是有多恶心.所以这里提供一种思路来对三方库进行适度的封装,这里以封装MJRefresh为例.
涉及到的技术和设计模式
抽象
工厂模式
代理
单例
反射
运行时
没有封装之前
MJRefresh
是一个非常优秀的三方库,本身做了非常良好的封装,但是当我们使用它的时候会是如下这个样子:
self.tableView.mj_header = [TYRefreshHeader headerWithRefreshingBlock:^{
[weakSelf getList];
}];
self.tableView.mj_footer = [TYRefreshFooter footerWithRefreshingBlock:^{
[weakSelf getMoreList];
}];
[self.tableView.mj_header endRefreshing];
[self.tableView.mj_footer resetNoMoreData];
[self.tableView.mj_footer endRefreshing];
[self.tableView.mj_footer endRefreshingWithNoMoreData];
分析这里很恶心的几个地方:
1.虽然只有上面六个API,但是他们可能随机出现在当前文件的任何地方,看起来很松散.
2.如果将来不用MJRefresh
了,我们还得去找这些API出现的地方,一个个改......
3.这些代码都掺杂在了业务当中,其实是可以抽取的.
封装的思路
理想的效果是:我们只需要一个外部的refreshManager来实现刷新功能即可,至于是停止刷新还是开始刷新都应该让这个manager内部自己去管理
,那么可以得出如下理想的调用方式:
//调用
- (void)getList{
[self.refreshManager refresh];
}
//初始化这里是通过block进行回调,也可以使用delegate进行回调
- (TestRefreshManager *)refreshManager{
if (!_refreshManager) {
WeakSelf
_refreshManager = [[TestRefreshManager alloc] initWithTarget:self.tableView requestParams:@{@"url" : @"http://fdsafd",@"params" : @{@"name" : @"allen"}} callBackValue:^(id value) {
weakSelf.dataArray = value;
[weakSelf.tableView reloadData];
}];
}
return _refreshManager;
}
这样,刷新的API就不会掺杂在业务当中,而且对外暴露的东西很少就一个manager而已,咋一看和MJRefresh
半毛钱关系都没.其实不然,这里整理了一个内部的实现原理图:
这是很早之前的一张草图了,很糙很糙......解释一下各个模块的意思:
1.RefreshProtocol:这个协议规定了刷新需要实现的方法,其实就是一个抽象,便于扩展.
#import <Foundation/Foundation.h>
/*此协议规定了刷新必须实现的方法*/
@class EWBaseRefreshManager;
@protocol EWRefreshProtocol <NSObject>
@required
- (instancetype)initWithTarget:(UIScrollView *)tableView withRefreshManager:(id)manager;
- (void)refresh;
/**
* 停止刷新
*/
- (void)endHeaderRefreshing;
- (void)endFooterRefreshing;
- (void)endWithNoMoreData;
/**
* 开始刷新
*/
- (void)beginRefreshing;
- (void)refreshFooter;
- (void)refreshHeader;
@end
2.MJRefresh/OtherLib:这个类是一个继承于NSObject的类,遵守了RefreshProtocol
协议,并且这个类直接耦合MJRefresh,在对应的协议方法下实现了对应底层的刷新API.
#import "EWRefreshInstanByMJRefresh.h"
#import "DiyFooter.h"
#import "DiyHeader.h"
#import <MJRefresh.h>
#import "EWChildRefreshProtocol.h"
@interface EWRefreshInstanByMJRefresh()
@property (nonatomic , strong) UIScrollView *tableView;
@property (nonatomic , assign) SEL headerAction;
@property (nonatomic , assign) SEL footerAction;
@property (nonatomic , strong) id refreshManager;
@property (nonatomic,strong) DiyFooter *footer;
@end
static NSString *const KEWHeaderAction = @"refreshTargetHeader";
static NSString *const KEWFooterAction = @"refreshTargetFooter";
#define WeakSelf __weak __typeof(self)weakSelf = self;
@implementation EWRefreshInstanByMJRefresh
- (instancetype)initWithTarget:(UIScrollView *)tableView withRefreshManager:(id)manager{
if (self = [super init]) {
self.refreshManager = manager;
self.tableView = tableView;
self.tableView.mj_footer = self.footer;
}
return self;
}
- (void)refresh{
[self refreshHeader];
[self beginRefreshing];
}
/**
* 刷新头部数据
*/
- (void)refreshHeader{
if ([self.tableView.mj_header isRefreshing]) {
self.tableView.userInteractionEnabled = NO;
}else{
self.tableView.userInteractionEnabled = YES;
}
self.tableView.mj_header = [DiyHeader headerWithRefreshingBlock:^{
WeakSelf
if ([self.refreshManager respondsToSelector:@selector(refreshTargetHeader)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks" //去除警告
[weakSelf.refreshManager performSelector:weakSelf.headerAction];
#pragma clang diagnostic pop
}
}];
}
/**
* 刷新底部数据
*/
- (void)refreshFooter{
if ([self.refreshManager respondsToSelector:@selector(refreshTargetFooter)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks" //去除警告
[self.refreshManager performSelector:self.footerAction];
#pragma clang diagnostic pop
}
}
/**
* 停止刷新
*/
- (void)endHeaderRefreshing{
[self.tableView.mj_header endRefreshing];
}
- (void)endFooterRefreshing{
[self.tableView.mj_footer endRefreshing];
}
- (void)endWithNoMoreData{
[self.tableView.mj_footer endRefreshingWithNoMoreData];
}
/**
* 开始刷新
*/
- (void)beginRefreshing{
[self.tableView.mj_header beginRefreshing];
}
#pragma mark - getter
- (DiyFooter *)footer{
if (!_footer) {
_footer = [DiyFooter footerWithRefreshingBlock:^{
WeakSelf
[weakSelf refreshFooter];
}];
}
return _footer;
}
- (SEL)headerAction{
SEL action = NSSelectorFromString(KEWHeaderAction);
return action;
}
- (SEL)footerAction{
SEL action = NSSelectorFromString(KEWFooterAction);
return action;
}
@end
3.Factory:工厂中,这里会传入一个字符串,利用反射得到这个实例refreshInstance
,这里并不知道这个类到底是哪个三方提供的,但是是遵守了RefreshProtocol
这个协议的.
#import "EWRefreshFactory.h"
#import "EWChildRefreshProtocol.h"
@implementation EWRefreshFactory
static EWRefreshFactory *singleton = nil;
+ (instancetype)shareInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[self alloc] init];
});
return singleton;
}
/**
* 返回的是一个三方库的实例对象,必须遵守对应协议
*/
- (id)refreshInstanceByTarget:(NSString *)targetClassString tableView:(UIScrollView *)tableView withRefreshManager:(id<EWChildRefreshProtocol>)manager{
id<EWRefreshProtocol> refreshInstance = [[NSClassFromString(targetClassString) alloc] initWithTarget:tableView withRefreshManager:manager];
//如果没有遵守请求的协议就返回nil
if (![refreshInstance conformsToProtocol:@protocol(EWRefreshProtocol)]) {
return nil;
}
return refreshInstance;
}
4.BaseManager:这是个基类,利用上面的refreshInstance
进行刷新操作,提供了外部业务需要的参数,page,以及数据回调功能,需要继承这个类,才能进行刷新操作.
#import <Foundation/Foundation.h>
#import "EWChildRefreshProtocol.h"
typedef void(^callBackValue)(id value);
@protocol EWBaseRefreshManagerDelegate <NSObject>
- (void)didRefreshWithCallBackValue:(id)value;
- (void)didRefreshWithFailedCallBackValue:(id)value;
@end
@interface EWBaseRefreshManager : NSObject
//传进来的View
@property (nonatomic , strong) UIScrollView* tableView;
//回调的block
@property (nonatomic , copy) callBackValue callbackValue;
//传入的参数字典,包含url和请求参数,具体key值自定义即可
@property (nonatomic , strong) NSDictionary *requestParams;
//回调的数据
@property (nonatomic , strong) NSMutableArray *dataArray;
//刷新的页码
@property (nonatomic , assign) NSInteger page;
@property (nonatomic , weak) id<EWBaseRefreshManagerDelegate> delegate;
@property (nonatomic , weak) id<EWChildRefreshProtocol> childManager;
@property (nonatomic , strong) NSError *error;
/**
* 初始化方法
*/
- (instancetype)initWithTarget:(id)tableView requestParams:(NSDictionary *)requestParams callBackValue:(void(^)(id value))callBackValue;
/**
* 开始刷新,封装了beginRefreshing 和 refreshHeader方法
*/
- (void)refresh;
/**
* 回调数据的方法
*/
- (void)callBackData;
- (void)callBackFailed;
/**
* 停止刷新
*/
- (void)endHeaderRefreshing;
- (void)endFooterRefreshing;
- (void)endWithNoMoreData;
/**
* 取消请求
*/
- (void)cancellRefresh;
@end
5.ChildRefreshProtocol:这个协议规定了两个协议方法,用来接收外界获取列表的API方法,继承``BaseManager```的子类必须遵守这个协议,里面有两个方法:
#import <Foundation/Foundation.h>
@protocol EWChildRefreshProtocol <NSObject>
@required
#pragma mark - 子类实现这个两个方法完成上拉下拉刷新
- (NSURLSessionDataTask *)refreshTargetHeader;
- (NSURLSessionDataTask *)refreshTargetFooter;
@end
也许,从上述的结构图中,你就能体会到这种设计的优势在哪里了.当我要更换这个三方库时,只需要替换一个文件而已,如果也需要更新API,只需要在对应的protocol中添加相应的协议方法即可.
我这里只讲述实现思路,具体的代码请看我的demo.
Talk is cheap,show me the code.
Code在这里
网友评论