一、前言
最近一段时间,想着封装一套网络库,在查资料时看到了YTKNetwork
,下载地址,它基本包含的网络请求的所有功能,而且在GitHub
上的星星数也都有了5k+了,我就仔细的看了看,嗯,真香,基本上能用的功能全都有了!越看越想把自己工程里的网络库给换掉(自己封装的AFNetworking),但又发现了YTKNetwork
的用法跟我项目中本身有点不兼容,于是乎我就想把它再次封装一下,毕竟按照它提供的用法,一个接口就得有一个对应的类来控制,小业务量的App还好,但是大项目岂不是要创建一堆的接口类,我想应该有不少小伙伴有同样的问题,伴随着这个问题为出发点,开始了再次封装的尝试。
二、思路
一个接口由一个类来控制,emm...,这是要解决的最重要的一个问题。而我要做的是由一个类来控制所有接口。首先我想到的就是写一个接口类,然后每个请求对应接口类里的一个方法,然后再调用YTKNetwork
来转发。其它用法要尽量不改变YTKNetwork
,以避免因为再封装原因而导致YTKNetwork
内部逻辑的bug。
三、开始搬砖
1.解决YTKNetwork
所有参数都是通过方法来传递
在官方示例中,我们可以看到,方法请求的地址(后缀地址)、请求参数等都只能通过重写- (NSString *)requestUrl
与- (nullable id)requestArgument
,我在这里的解决思路是写一个YTKRequest
的分类,然后再添加两个属性fUrl
、param
,然后分别在两个方法返回这两个属性值。
//
// YTKRequest+FuHttp.h
// FuHttpDemo
//
// Created by 付海龙 on 2019/3/25.
// Copyright © 2019 付海龙. All rights reserved.
//
#import "YTKRequest.h"
NS_ASSUME_NONNULL_BEGIN
@interface YTKRequest (FuHttp)
@property (nonatomic, copy) NSString *fUrl; //requestUrl
@property (nonatomic, copy) NSDictionary *param; //requestArgument
@end
//
// YTKRequest+FuHttp.m
// FuHttpDemo
//
// Created by 付海龙 on 2019/3/25.
// Copyright © 2019 付海龙. All rights reserved.
//
#import "YTKRequest+FuHttp.h"
#import "FuHttpMethod.h"
#import "YYKit.h"
@implementation YTKRequest (FuHttp)
static NSString *fUrlKey = @"F_URL_KEY";
static NSString *paramKey = @"PARAM_KEY";
static NSString *fuHttpTypeKey = @"RESULT_BLOCK_KEY";
- (void)setFUrl:(NSString *)fUrl {
objc_setAssociatedObject(self, &fUrlKey, fUrl, OBJC_ASSOCIATION_COPY);
}
- (NSString *)fUrl {
return objc_getAssociatedObject(self, &fUrlKey);
}
- (void)setParam:(NSDictionary *)param {
objc_setAssociatedObject(self, ¶mKey, param, OBJC_ASSOCIATION_COPY);
}
- (NSDictionary *)param {
return objc_getAssociatedObject(self, ¶mKey);
}
pragma mark - 重写YTKRequest方法
- (NSString *)requestUrl {
return self.fUrl;
}
- (nullable id)requestArgument {
return self.param;
}
- (NSTimeInterval)requestTimeoutInterval {
//超时时间
return 10;
}
- (nullable NSDictionary<NSString *, NSString *> *)requestHeaderFieldValueDictionary {
return 请求头;
}
2.数据传递
我们封装了YTKNetwork
那请求回来的数据也在封装的类里,解决数据传递,回传给需要用到的地方,在这个分类里,使用了block
typedef void(^RequestResult)(BOOL isSuccess, id object);
@property (nonatomic, copy) RequestResult resultBlock; //数据传递
static NSString *resultBlockKey = @"RESULT_BLOCK_KEY";
- (void)setResultBlock:(void (^)(BOOL, id _Nonnull))block {
objc_setAssociatedObject(self, &resultBlockKey, block, OBJC_ASSOCIATION_COPY);
}
- (RequestResult)resultBlock {
return objc_getAssociatedObject(self, &resultBlockKey);
}
3.GET
与POST
请求处理
官方示例中,请求发送的是GET
还是POST
需要通过- (YTKRequestMethod)requestMethod
来指定,这里我们也可以参考上面的方法
@property (nonatomic, copy) NSNumber *requestType; //请求类型 Get or Post
static NSString *requestTypeKey = @"REQUEST_TYPE_KEY";
- (void)setRequestType:(NSNumber *)requestType
{
objc_setAssociatedObject(self, &requestTypeKey, requestType, OBJC_ASSOCIATION_COPY);
}
- (NSNumber *)requestType
{
return objc_getAssociatedObject(self, &requestTypeKey);
}
- (YTKRequestMethod)requestMethod {
return self.requestType.integerValue;
}
4.获得YTKRequest
对象
基本上需要用到的参数及方法都准备好了,剩下的就是将生成YTKRequest
对象并将对应的属性赋值,这里我添加了一个类方法来实现
- (void)setRequestType:(NSUInteger)type param:(NSDictionary *)param requestType:(NSInteger)requestType replace:(NSString *)replace {
NSString *url = [[FuHttpMethod sharedMethod] typeMethod:type];
if ([url containsString:@"<id>"]) {
if (replace.isNotBlank) {
self.fUrl = [url stringByReplacingOccurrencesOfString:@"<id>"
withString:replace];
}else {
self.fUrl = [url stringByReplacingOccurrencesOfString:@"<id>"
withString:@""];
}
}else {
self.fUrl = url;
}
self.param = param;
self.FuHttpType = [NSNumber numberWithUnsignedInteger:type];
self.requestType = [NSNumber numberWithInteger:requestType];
}
+ (YTKRequest *)getYTK:(NSUInteger)type param:(NSDictionary *)param requestType:(NSInteger)requestType replace:(NSString *)replace {
YTKRequest *ytk = [[YTKRequest alloc] init];
[ytk setRequestType:type
param:param
requestType:requestType
replace:replace];
return ytk;
}
5.接口类的实现
接口类的实现部份比较简单,没什么可说的,就是写上对应的Get
和Post
的调取方法,声明一个代理,方便通知请求开始
、请求成功
和请求失败
的三种情况,然后子类继承这个类,子类主要就是做一个接口对应一个方法的实现
//
// FuHttpRequest.h
// FuHttpDemo
//
// Created by 付海龙 on 2019/3/25.
// Copyright © 2019 付海龙. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "YTKNetwork.h"
#import "FuHttpMethod.h"
#import "FuError.h"
NS_ASSUME_NONNULL_BEGIN
@class FuHttpRequest;
@protocol FuHttpRequestDelegate <NSObject>
@optional
- (void)httpStartRequest:(YTKBaseRequest *)requestObject;
- (void)httpFinishRequest:(YTKBaseRequest *)requestObject object:(id)object;
- (void)httpFailedRequest:(YTKBaseRequest *)requestObject error:(FuError *)error;
@end
@interface FuHttpRequest : NSObject
@property (nonatomic, weak) id<FuHttpRequestDelegate> delegate;
@property (nonatomic, assign) NSInteger requestTag;
+ (id)initWithDelegate:(id<FuHttpRequestDelegate>)delegate;
- (YTKRequest *)requestGet:(NSUInteger)type param:(nullable id)param replace:(nullable NSString *)replace;
- (YTKRequest *)requestPost:(NSUInteger)type param:(nullable id)param replace:(nullable NSString *)replace;;
@end
//
// FuHttpInterface.h
// FuHttpDemo
//
// Created by 付海龙 on 2019/3/25.
// Copyright © 2019 付海龙. All rights reserved.
//
#import "FuHttpRequest.h"
#import "YTKRequest+FuHttp.h"
NS_ASSUME_NONNULL_BEGIN
@interface FuHttpInterface : FuHttpRequest
- (YTKRequest *)http_testPost:(NSString *)str;
- (YTKRequest *)http_testGet;
@end
NS_ASSUME_NONNULL_END
//
// FuHttpInterface.m
// FuHttpDemo
//
// Created by 付海龙 on 2019/3/25.
// Copyright © 2019 付海龙. All rights reserved.
//
#import "FuHttpInterface.h"
#import "YYKit.h"
@implementation FuHttpInterface
- (YTKRequest *)http_testPost:(NSString *)str {
NSDictionary *param = nil;
if (str.isNotBlank) {
param = @{@"query":str};
}
return [self requestPost:TEST_POST param:param replace:nil];
}
- (YTKRequest *)http_testGet {
return [self requestGet:TEST_GET param:nil replace:nil];
}
@end
我这里简单写了两个例子,按现在这样,如果有新的接口,只需要在这里编写对应的调用方法就可以了
6.关于TEST_GET
和TEST_POST
在上面的接口类里,并没有看到传入的requestUrl
,而代替它的就是TEST_GET
和TEST_POST
,我在封装的时候顺便也把接口的声明也做了处理,在FuHttpMethod
类里,小伙伴们在用的时候,只需要在.h
文件里的枚举添加一个case
,在.m
里添加对应的requestUrl
就可以了,至于其绑定逻辑我就不多介绍了,很简单,想看的小伙伴可以看我在文章最后上传的Demo。
typedef NS_ENUM(NSInteger, FuHttpType) {
FU_HTTP_BEGIN = -1,
TEST_POST, //测试POST请求
TEST_GET, //测试GET请求
FU_HTTP_END,
};
HTTP_INFO(@"test/post/requestUrl", TEST_POST);
HTTP_INFO(@"test/get/requestUrl", TEST_GET);
7.测试一下
在AppDelegate.m
的didFinishLaunchingWithOptions
方法里添加对YTKNetworkConfig
的配置,这里没有改动,按照YTKNetwork
的用法来就好
YTKNetworkConfig *config = [YTKNetworkConfig sharedConfig];
/*
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"证书名" ofType:@"后缀"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
config.securityPolicy.allowInvalidCertificates = YES;
config.securityPolicy.validatesDomainName = NO;
config.securityPolicy.pinnedCertificates = [NSSet setWithObject:certData];
*/
config.baseUrl = @"你的baseUrl";
在ViewController.m
的viewDidLoad
里,先对接口类进行初始化,进行测试
- (void)viewDidLoad {
[super viewDidLoad];
self.http = [[FuHttpInterface alloc] init];
// [self postTest];
[self getTest];
}
- (void)postTest {
[[self.http http_testPost:@"abc"] setResultBlock:^(BOOL isSuccess, id _Nonnull object) {
if (isSuccess) {
NSLog(@"请求成功");
}else {
NSLog(@"请求失败");
}
}];
}
- (void)getTest {
[[self.http http_testGet] setResultBlock:^(BOOL isSuccess, id _Nonnull object) {
if (isSuccess) {
NSLog(@"请求成功");
}else {
NSLog(@"请求失败");
}
}];
}
当你能看到控制台有-[FuHttpRequest requestSuccess:request:] [Line 86] 数据信息:
这样的字样时就代表请求没问题,而后面就是对应请求回来的json
,我在数据处理那写了不少的逻辑,基本上都与目前我做的项目有关,如果觉得与你的项目不太合适,小伙伴们可以自己稍加修改。
四、额外功能
在Json
转Model
时,我使用了YYModel
,相信很多小伙伴都给接口写过对应的Model
文件,工作量也不小,我在Demo
里添加了一个FuAnalysis
类,用来做这个事,当你的工程中没有对应的Model
时,就会在控制台输出将要添加类的内容;如果存在,则使用YYModel
解析成对应的对象。文件名及相应的类的命名都是以接口名为基础,例如:requestUrl
是abc/def
,那么文件名就是DefModel
,而里面的类的名字就是Def_Item
测试一下,写一个Json
{"RC":1,"data":{"array":[{"name":"fu","address":"北京"},{"name":"hai","tel":8888},{"name":"long","is_working":true}]}}
FuAnalysis *analysis = [[FuAnalysis alloc] init];
id object = [analysis analysisData:@"abc/def" object:[jsonStr jsonValueDecoded]];
---
ClassName : DefModel
.h文件
@interface Def_Array_Item : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *address;
@property (nonatomic, strong) NSNumber *tel;
@property (nonatomic, strong) NSNumber *is_working;
@end
@interface Def_Data_Item : NSObject
@property (nonatomic, strong) NSArray *array;
@end
@interface Def_Item : NSObject
@property (nonatomic, strong) NSNumber *RC;
@property (nonatomic, strong) Def_Data_Item *data;
@end
---
.m文件
@implementation Def_Array_Item
@end
@implementation Def_Data_Item
+ (NSDictionary *)modelContainerPropertyGenericClass {
return @{
@"array":[Def_Array_Item class],
}
}
@end
@implementation Def_Item
@end
---
工程里有对应的Model
文件
if ([object isKindOfClass:[Def_Item class]]) {
Def_Item *item = (Def_Item *)object;
[item.data.array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
Def_Array_Item *item = (Def_Array_Item *)obj;
NSLog(@"%@", item.name);
}];
}
2019-03-27 17:46:25.116733+0800 FuYTKDemo[7311:458718] fu
2019-03-27 17:46:25.116862+0800 FuYTKDemo[7311:458718] hai
2019-03-27 17:46:25.116954+0800 FuYTKDemo[7311:458718] long
测试成功!
有关代码中出现的<id>
,它是我们项目中requestUrl
需要拼接的参数,例如在Post请求里,test/post/123
,这样的123
直接写到requestUrl
里就不太对了,因此我加入了替换元素<id>
在请求发送之前再将requestUrl
替换好。
最后,欢迎小伙伴在评论留言,喜欢的点个心!