美文网首页
[iOS]如何封装第三方库(非Appdelegate启动)(一)

[iOS]如何封装第三方库(非Appdelegate启动)(一)

作者: 未来行者 | 来源:发表于2018-03-27 17:33 被阅读40次

前言

通常我们都会在项目中使用三方库,它们能帮我们提高开发效率.但是随着时间的发展,你使用的某个三方库可能会停止维护.随着系统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在这里

相关文章

网友评论

      本文标题:[iOS]如何封装第三方库(非Appdelegate启动)(一)

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