美文网首页
[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