[iOS]自己实现一个简单的离散化网络请求库

更新:梳理了库中的耦合文件,可以直接提取网络库文件夹进行使用,优化了缓存设计.
鸣谢:本人是在认真研读casa的iOS应用架构谈 网络层设计方案之后,得出的相应的思路,并在此基础上做出了自己的需求延展.在此十分感谢这位反革命工程师的真知灼见!

首先,什么是离散化管理?在鸣谢的这篇文章里,casa已经做出了一个比较明确的解释:每一个请求的API都对应一个类来管理,不同于将请求的url,参数等都放入一个方法(又称集约式管理)中来管理.好处显而易见:便于维护,控制.

集约式是这样的:

[manager GET:url parameters:param progress:nil success:^(NSURLSessionDataTask * _Nonnull task, NSDictionary* responseObject) {
        success ? success(responseObject) : nil;
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        failure ? failure(error) : nil;
        NSLog(@"请求失败-->%@",error);
    }];

离散化是这样的:

@implementation TestAPIManger
//请求方式
- (EWRequestType)requestType{
    return EWAPIRequestTypeGet;
}
//请求的方法名
- (NSString *)requestMethod{
    return @"video";
}
//请求需要的参数
- (NSDictionary *)params{
    return @{@"type":@"JSON"};
}
//是否需要缓存
- (NSNumber *)shouldCache{
    return @180;
}
//额外参数,根据需求而定
- (NSString *)memberCode{
    return @"";
}
//是否需要加载动画
- (NSDictionary *)animationTargetAction{
    return @{
             EWRequestAnimationTarget : @"ZNRequestAnimation",
             EWShowHudAnimation : @"showHudAnimation",
             EWHideHudAtWindow : @"hideHudAtWindow"
             };
}
//是否需要拼接请求头
- (NSDictionary *)headerDict{
    return nil;
}
@end

离散化的调用方法是这样的,这里使用代理的方式来回调结果,目的是控制灵活性,方便管理,bug排查:

TestAPIManger *testApi = [[TestAPIManger alloc] init];
testApi.delegate = self;
[testApi loadData];

这里来进行一波解释:
TestAPIManger->进行网络请求的实例,封装了请求需要的url,参数等
testApi.delegate->进行网路请求数据回调的代理
loadData->开启网络请求的方法.

正式开始封装之路:
疑问1:如何设计这个APIManager?
这里是设计了一个EWAPIBaseManager,这个类的作用是定义请求APIManager请求的基本方法,作为一个父类,之后的每个请求实例都继承自这个类.
这里设计出来的样子暂时是这个样子:

//回调的代理,需要遵守EWAPICallBackProtocol协议
@property (nonatomic , weak) id<EWAPICallBackProtocol> delegate;

//遵守协议的子类,须遵守EWAPIManagerProtocol协议
@property (nonatomic , weak) NSObject<EWAPIManagerProtocol> *childManager;
//自定义response用来统一保存数据和error
@property (nonatomic , strong) EWResponse *response;

//外部传入的参数
@property (strong,nonatomic) NSMutableDictionary *outerParams;

//是否需要动画
@property (nonatomic , strong) NSDictionary *animationTargetAction;

//数据过滤的方法,必须要遵守EWDataFilterProtocol协议

- (id)filterDataWithFilter:(id<EWDataFilterProtocol>)filter;

//是否需要缓存
- (NSNumber *)shouldCache;
/**
 * 加载数据
 */
- (void)loadData;
/**
 * 取消请求
 */
- (void)cancelAllRequest;

其中加载数据作为一个基本功能被放到了这里,方法名为loadData.
这里用到了几个协议:

EWAPICallBackProtocol:完成请求回调的协议
EWAPIManagerProtocol:管理每个请求的参数,url等
EWDataFilterProtocol:定义了数据过滤的方法

疑问2:每一个APIManager如何管理请求URL和参数?
解决方式:声明一个协议EWAPIManagerProtocol,声明如下方法

typedef NS_ENUM(NSInteger,EWRequestType){
    EWAPIRequestTypeGet = 0,
    EWAPIRequestTypePost = 1,
    EWAPIRequestTypeUploadImage = 2
};
@protocol EWAPIManagerProtocol <NSObject>
@required
//请求方式
- (EWRequestType)requestType;
//请求的参数
- (NSDictionary *)params;
//请求的方法名
- (NSString *)requestMethod;
//请求后完整的拼接参数
- (NSDictionary*)paramsForAPI;
//自定义的requestheader
- (NSDictionary *)headerDict;
@optional
//物业接口可能会有membercode
- (NSString *)memberCode;
@end

然后创建一个NSObject类,遵守这个协议,就是上面的TestAPIManger,并实现协议中的方法,那么这些参数都被保存在了这个TestAPIManger了.

疑问3:如何将APIManager保存的参数传递到网络请求中?
这里我要重提一下casa的观点,离散化的网络层其实本质是集约调用,我们只不过是在底层通过delegate将返回的数据进行了转发而已,因为底层变动不大,所以如此做无伤大雅.

在上面的loadData方法中,是如下实现方式:

/**
 * 执行请求任务
 */
- (void)loadData{
    switch (self.childManager.requestType) {
            
        case EWAPIRequestTypeGet:
            APIRequest(Get)
            break;
            
        case EWAPIRequestTypePost:
            APIRequest(Post)
            break;
            
        case EWAPIRequestTypeUploadImage:
            APIRequest(PostImage)
            break;
        default:
            break;
    }
}

其中APIRequest()是一个宏,该宏实现了网络请求:

/**
 * 定义完成请求的宏
 */
#define APIRequest(requestType) \
{\
EW_WeakSelf\
    [self startRequestAnimation];\开启动画
[self.apiRequest sendRequestBy##requestType##WithParams:self.childManager.paramsForAPI success:^(EWResponse *response) {\
    [weakSelf cancellReqeustAnimation];\关闭动画
[weakSelf requestSuccess:response];\
} fail:^(EWResponse *response) {\
    [weakSelf cancellReqeustAnimation];\关闭动画
    [weakSelf requestFailed:response];\
}];\
}

在这里self.childManager.paramsForAPI就将APIManager中的参数传递过去了.至于原理,在于将作为EWBaseAPIManager的init方法重写了,当我们初始化子类TestAPIManager的时候,父类EWBaseAPIManager中的childManager就已经成为了TestAPIManager:

- (instancetype)init
{
    self = [super init];
    if (self) {
        //初始化的时候,childManager即为当前的子类,完成请求参数的传递
        if ([self conformsToProtocol:@protocol(EWAPIManagerProtocol)]) {
            self.childManager = (NSObject<EWAPIManagerProtocol> *)self;
        }
    }
    return self;
}

疑问4:底层如何进行数据请求?
这里我设计了一个分发请求的类EWAPIRequest,里面目前只定义了三个方法:

/**
 *  get请求
 *
 *  @param params 传入的参数,必须包含url,请求类型,参数,以及cache时间(没有就填@0)
 *  @param success 请求成功后的回调(请将请求成功后想做的事情写到这个block中)
 *  @param failure 请求失败后的回调(请将请求失败后想做的事情写到这个block中)
 */
- (NSURLSessionDataTask *)sendRequestByGetWithParams:(NSDictionary *)params success:(void (^)(EWResponse *))success fail:(void (^)(EWResponse *))failure;
/**
 *  post请求
 *
 *  @param params 传入的参数,必须包含url,请求类型,参数,以及cache时间(没有就填@0)
 *  @param success 请求成功后的回调(请将请求成功后想做的事情写到这个block中)
 *  @param failure 请求失败后的回调(请将请求失败后想做的事情写到这个block中)
 */
- (NSURLSessionDataTask *)sendRequestByPostWithParams:(NSDictionary *)params success:(void (^)(EWResponse *))success fail:(void (^)(EWResponse *))failure;

/**
 *  图片上传
 *
 *  @param params 传入的参数,必须包含url,图片内容,图片key为EWUploadImageKey
 *  @param success 请求成功后的回调(请将请求成功后想做的事情写到这个block中)
 *  @param failure 请求失败后的回调(请将请求失败后想做的事情写到这个block中)
 */
- (NSURLSessionDataTask *)sendRequestByPostImageWithParams:(NSDictionary *)params success:(void (^)(EWResponse *))success fail:(void (^)(EWResponse *))failure;

调用的地方在上方的请求宏中:

self.apiRequest sendRequestBy##requestType##WithParams:self.childManager.paramsForAPI success:^(EWResponse *response)

进入到EWAPIRequest的方法实现中,我们可以看到里面是这样实现的:

NSURLSessionDataTask *dataTask = nil;
    
    //通过工厂类获得请求的实例,实例必须遵循这个请求的协议
    //这里采用硬编码的方式,决定到底是用什么库来进行网络请求,目的在于方便切换网络库
    //KEWRequestByAFN表示采用AFN这个网络库请求数据
    
    id<EWNetworkRequestProtocol> requestInstance = [[EWRequestInstanceFactory shareInstance] requestInstance:KEWRequestByAFN];
    
    //请求数据
    dataTask = [requestInstance requestByGetWithParams:params success:^(id responseObject) {
        
        //生成统一管理网络数据的response
        //存入回调的数据
        EWResponse *response = [[EWResponse alloc] initWithResopnseObject:responseObject andError:nil];
        
        //回调这个response
        success ? success(response) : nil;
    } fail:^(NSError *error) {
        
        //存入错误信息
        EWResponse *errorResponse = [[EWResponse alloc] initWithResopnseObject:nil andError:error];
        
        //回调这个response
        failure ? failure(errorResponse) : nil;
        SLog(@"请求失败-->%@",error);
    }];

这里涉及到了几个协议和类,一一解释一下
EWNetworkRequestProtocol:这个协议定义了最底层网络请求库需要遵守的方法,这里我用的AFN作为底层请求库.
KEWRequestByAFN:这是个const常量,表示当前的请求库是基于AFN的
EWRequestInstanceFactory:这是个工厂类,为了返回遵守EWNetworkRequestProtocol协议的网络库的实例,底层是通过反射KEWRequestByAFN这个字符串获得请求的实例,如果你要切换网络库,只需要新增一个请求类并且再定义一个const常量,在EWRequestInstanceFactory中替换KEWRequestByAFN即可.
EWResponse:这个类的作用是用来统一保存请求的数据和错误信息

拿到请求的实例requestInstance之后就调用EWNetworkRequestProtocol中的requestByGetWithParams方法来进行网络请求.之后就是将参数传入AFN请求类中实现最终的请求,并回调结果.

疑问5:回调结果的处理?
EWAPIBaseManager中,我用了两个私有方法在请求的宏里对回调结果进行转发,然后将结果回调给EWAPICallBackProtocol协议中的方法:managerCallBackDidSuccess,managerCallBackDidFailed.

//回调成功的response,里面保存了请求成功的数据
- (void)requestSuccess:(EWResponse *)response{
//将response赋值给apiManager
    self.response = response;
    if ([self.delegate respondsToSelector:@selector(managerCallBackDidSuccess:)]) {
        [self.delegate managerCallBackDidSuccess:self];
    }
}
//回调失败的response,里面保存了请求失败的错误信息
- (void)requestFailed:(EWResponse *)response{
//将response赋值给apiManager
    self.response = response;
    if ([self.delegate respondsToSelector:@selector(managerCallBackDidFailed:)]) {
        [self.delegate managerCallBackDidFailed:self];
    }
}

就这样,就实现了整个网络请求的过程.至于这里为什么是回调response而不是直接回调responseObject,原因是用response可以统一管理回调数据和错误信息,就不需要再定义responseObject和error的变量了.

在这个库里面我还添加了加载动画,缓存,数据过滤等功能,有兴趣可以自己研究下,demo在这里.

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

推荐阅读更多精彩内容