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

前言

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容

  • 今天是2017年05月04日,或是我职业的关系,强迫症一样的把日期写成这样,第一次在手机应用商店下载了简书,注册也...
    大狮兄0539阅读 113评论 0 1
  • 我妈妈对饮食是不怎么操心的,只管把一家大小喂饱即可,至于口味、搭配、营养这些,从来是没什么讲究的。爸爸倒是时不时抗...
    伊萨卡阅读 279评论 0 2
  • 主見~成長~ 有一句話説, 世界上有九分之八的人不同於你, 你要接納他們; 還有九分之一的人與你相同, 所以你要接...
    珈玶阅读 296评论 0 4
  • 我和你,相识有10年之久。从点头之交到推心置腹,我们两个都不记得是怎么就走到了一起。你经常笑着说,我跟他认识多久,...
    carol_飘摇阅读 265评论 0 0
  • 对于生活的期望我做到了哪些? 2016,开了眼界,分为两段。 出国,还有去三亚。 2017学好英语,把琴弹好。 关...
    lygly9阅读 189评论 0 0