LSYNetworking:基于AFNetworking的网络请求框架-基础篇

给大家分享一下我封装的一个基于AFNetworking的网络请求库 LSYNetworking

这个库的特点是,非常简单!非常的容易上手!但是却...非常的强大!
这里的强大,并不是说这个库使用了多么高深的技术(高深的都在AFN里😒),而是说,这个库从广大开发者的实际使用需求出发,考虑到了多种业务需求,将大部分工作(重复的工作)都替开发者做了,只需要简单的几行代码,就可以完成一次网络请求,而你想实现的复杂功能,它几乎都支持。

官方一点的说就是:

LSYNetworking实现了对AFNetworking 的高度封装和对网络请求的高度抽象,可应对各种复杂的业务需求,使用者可根据自身的业务需求,在请求过程中的一些关键节点插入自己的逻辑,处理请求的各种数据等

这一篇主要讲一些基础的使用,更多内容可以看一下进阶篇

下面是详细的使用说明

创建你的BaseRequest


首先,我们需要有个BaseRequest,继承自LSYBaseRequest,这个BaseRequest将会是你所有请求的基类,一切拓展功能都将会在这里进行添加。

假设我们创建了YourBaseRequest,他看起来是这个样子的:

然后,我们可以在YourBaseRequestinit方法中做一些事情,比如,设置host(baseUrl):

self.host = @"https://api.lsy.com";

添加公共请求参数:

[self addExtraParamsWithDictionary:@{
    @"userId":@"11111111111"
}];

添加请求头:

self.httpRequestHeaders = @{
    @"version":@"1.0.0",
    @"platform":@"iOS"
};

这样,一些基本的功能就有了。

创建你的BaseResponse


有了BaseRequest,我们还需要有个BaseResponseBaseResponse需要实现LSYResponseProtocol协议,这个协议大概是这个样子的:

服务器的返回值结构,也许每个公司的都不一样,但基本上都能划分成三个部分,即,code,message,result。
code代表状态码(错误码),不同的值代表着不同的含义,一般200代表请求成功。
message代表错误信息。
result代表返回的数据,可以是用户信息,商品列表等。

假设我们创建了YourBaseResponse,他看起来是这个样子的:

然后,我们需要给YourBaseResponse添加一个用服务器返回的response作为参数的初始化方法,在这个方法里,我们需要将返回的response中的内容,对应到YourBaseResponse中的codemessageresult这三个字段。

假设你请求回来的数据结构是这样的:

{
    "resultcode" = 200,
    "resultmsg" = "请求成功!",
    "result" = {
        "name" = "xxx",
        "sex" = 1,
        "age" = 20
    }
}

那么你的初始化方法应该是这样的:


然后,你需要实现LSYResponseProtocol中的isRequestSuccess方法,你需要告诉我,你希望code的值为多少代表请求成功,假设200代表成功,那么这个方法的实现大概是这个样子的:

接下来是json自定义模型的相关协议的实现,这里有三个方法,分别是:

//将json转化为指定的model类的实例
- (instancetype)modelWithClass:(Class)resultClass json:(id)resultJson;
//将json转化为指定的model类实例的数组
- (NSArray *)modelArrayWithClass:(Class)elementClass json:(id)resultJson;
//将json转化为指定的model类实例的字典
- (NSDictionary *)modelDictionaryWithClass:(Class)elementClass json:(id)resultJson;

这样设计是为了解除LSYNetworking对模型转化库的耦合,你可以根据自己项目里的json转model的库进行实现,这个库可以是YYModel,Mantle,MJExtension等。

这里假设你用的是YYModel,那么实现看上去是这个样的:

- (instancetype)modelWithClass:(Class)resultClass json:(id)resultJson{
    return [resultClass yy_modelWithJSON:resultJson];
}

- (NSArray *)modelArrayWithClass:(Class)elementClass json:(id)resultJson{
    return [NSArray yy_modelArrayWithClass:elementClass json:resultJson];
}

- (NSDictionary *)modelDictionaryWithClass:(Class)elementClass json:(id)resultJson{
    return [NSDictionary yy_modelDictionaryWithClass:elementClass json:resultJson];
}

实现比较简单,直接用YYModel进行转化就行,需要说明一下的是,这三个协议方法是可选的,如果不实现方法,则对应的转换就不会生效。

到此为止,YourBaseResponse的工作就完成了,现在,我们需要将YourBaseResponseYourBaseRequest进行绑定,我们需要在YourBaseRequest里重写handleResponse:方法,来处理服务器返回的数据,将这个数据,转化为YourBaseResponse的实例:

- (id<LSYResponseProtocol>)handleResponse:(id)response{
    return [[YourBaseResponse alloc] initWithResponse:response];
}

至此,准备工作都已完成,接下来就可以开始发起一个网络请求了。

多个Host和多种Response结构的复杂情况


小公司一般只有一个Host,一种Response结构,那么定义一个BaseRequestBaseResponse就够用了。不过很多大厂,都会遇到一个项目里有多个Host的情况,甚至是,不同的HostResponse结构也不一样。

遇到这样的复杂情况,我们可以在方法中进行判断,根据判断结果选择正确的Host,使用正确的Response字段。

当然,如果Host不太多的话,也可以将一些单独的逻辑抽出来,再写一个XXXModuleBaseRequest继承自YourBaseRequest,如下图所示:

如果Host太多了,就舍弃设置Host + apiName的模式吧,直接将Host设置为整个请求的url也是没问题的(我目前负责的项目里就有几十种Host,不过好在返回值的结构都是一样的,所以不需要特殊处理);

创建一个网络请求实例


假设,这里有个请求,用好友的userId请求好友的个人基本信息,接口地址为https://api.lsy.com/user/firend_info,请求参数为friendId,请求方式为POST,返回值为个人信息的字典,大致结构如下:

{
    "resultcode" = 200,
    "resultmsg" = "请求成功!",
    "result" = {
        "user_id" = 11111111111,
        "name" = "xxx",
        "sex" = 1,
        "age" = 20
    }
}

我们先创建一个XXXFriendInfoRequest,继承自YourBaseRequest,然后给这个类添加一个属性friendId

@interface XXXFriendInfoRequest : YourBaseRequest
@property (assign, nonatomic) int friendId;
@end

接着,在初始化方法中设置请求的api地址

self.apiName = @"/user/firend_info";

当然,除了用属性的方式定义请求参数,你还可以用下边的方法添加一些额外的参数:

[self addExtraParamsWithDictionary:@{
    @"osVersion":@"iOS 14.0.1"
}];

我们一般会用addExtraParamsWithDictionary:添加一些不需要外部传递的参数,或者是一些写死的参数。

默认请求方式是POST,如果需要其他请求,可以设置method属性:

self.method = LSYRequestMethodTypeGET;

接下来,我们创建一个映射的模型XXXFirendInfo

@interface XXXFirendInfo : NSObject<YYModel>
@property (assign, nonatomic) int userId;
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int sex;
@property (assign, nonatomic) int age;
@end

因为返回的用户id的字段是user_id,而我们定义的属性是userId,所以我们需要用YYModel映射一下:

+(NSDictionary<NSString *,id> *)modelCustomPropertyMapper{
    return @{@"userId":@"user_id"};
}

模型定义好了,我们需要将XXXFirendInfo设置为XXXFriendInfoRequest的result类型,即,让返回值json自动转化为XXXFirendInfo

-(Class)resultClass{
    return XXXFirendInfo.class;
}

如果不设置,则默认返回json。

如果服务器返回的是数组或字典,需要转化成model的容器,则resultClass需要返回对应的类型,同时要实现elementClassIfResultIsCollection方法,返回容器里的元素的类,进行转化:

-(Class)resultClass{
    return NSArray.class;
}

-(Class)elementClassIfResultIsCollection{
    return XXXFirendInfo.class;
}

这样,返回的数据就会被转化成XXXFirendInfo的数组,至此,网络请求实例创建完毕。

发起一次网络请求


在需要请求的地方,创建一个XXXFriendInfoRequest的实例,设置friendId的值,然后调用startRequestWithSuccessBlock:failureBlock:方法进行网络请求:

XXXFriendInfoRequest *request = [[XXXFriendInfoRequest alloc] init];
request.friendId = 11111111;
[request startRequestWithSuccessBlock:^(XXXFirendInfo *responseData) {
    NSLog(@"Request success:%@",responseData);
} failureBlock:^(NSError * _Nonnull error) {
    NSLog(@"Request failed:%@",error);
}];

Request中定义的属性,会自动转换为请求参数,所以不需要额外的处理。

这样,一次请求就完成了,怎么样,是不是很简单!

发起一次Multipart请求


在开发过程中,我们有时候会需要Multipart请求,比如上传个人信息的时候可能会上传个头像,发朋友圈的时候会上传一些图片等。LSYNetworking将Multipart请求传输的内容抽象成了LSYRequestUploadItem,根据传输文件的不同类型,选择不同的子类进行初始化,赋值,请求。

我们模拟一下微信发朋友圈的请求,假设请求的类是WXTimelinePublishRequest,有个参数content,代表文字内容,图片最多上传9张,上传的图片对应的key是image0-image8,那么代码大致如下:

WXTimelinePublishRequest *request = [[XXXFriendInfoRequest alloc] init];
request.content = @"刚才我看到了一个UFO!";
LSYRequestUploadImage *imageItem = [[LSYRequestUploadImage alloc] init];
imageItem.key = @"image0";
imageItem.image = [[UIImage alloc] init];
[request uploadFileWithItems:@[imageItem] progressBlock:nil successBlock:^(id responseData) {
    NSLog(@"Request success:%@",responseData);
} failureBlock:^(NSError *error) {
    NSLog(@"Request failed:%@",error);
}];

请求失败类型判断


我们知道,网络请求失败的情况分为两种,一种是HTTP的错误,一种是业务逻辑的错误。
HTTP错误是系统级别的错误,我们最熟悉的就是404(Not found)
业务逻辑错误是指,请求其实已经通了,但是由于本次请求不符合一些业务上的逻辑,所以不能返回正确的数据,比如请求参数拼错了,比如userId不存在,比如用在未登录状态下请求了需要登录的接口。
HTTP的错误,一般只需要进行统一的toast弹窗提示,如"请求失败,请重试"。
而业务上的请求错误,一般需要客户端进行特殊的业务处理,比如用户需要在服务器返回未登录错误的时候调起登录逻辑。

LSYNetworking对失败情况进行了统一的回调,即不管是HTTP错误,还是业务逻辑错误,都会在failureBlock中进行回调,这样的好处是,避免了使用者需要在success回调中,还需要判断是否存在业务逻辑错误,而业务逻辑的错误,一般只需要在BaseRequest里进行公共的处理。

那么如何区分这两种错误呢?LSYNetworkingNSError写了个Category,增加了一些方法,我们可以通过isBusinessError方法来判断是否是业务逻辑错误,可以用extraInfo来获取服务器返回的额外信息(如果有的话),即Response中的result部分的内容,大致代码如下:

if (error.isBusinessError) {
    if (error.code == 302302) {
        //获取后台返回的其他信息进行处理
        NSDictionary *extraInfo = error.extraInfo;
    }
}

请求参数定制


既然定义的属性会自动转化为请求参数,那么我们如何控制哪些属性转化,哪些属性不要转化?如何控制只转化当前请求的参数,不要转化父类的参数?

LSYNetworkingNSObject写了个Category,实现了将obj中的属性转换为NSDictionary的功能,并且定义了LSYPropertysToDictionaryProtocol代理协议,用来控制转化后的字典内容。而LSYBaseRequest遵守了这个协议。

LSYNetworking默认会将当前类的属性进行转化,如果需要将父类,以及更之前的父类的属性也进行转化,则需要实现协议中的propertysDictionaryUntilClass方法,返回一个父类的Class,表示从当前的类开始,截止到返回的父类为止,比如,如果希望将XXXFriendInfoRequest的父类属性也作为请求参数,则需要这样写:

- (Class)propertysDictionaryUntilClass{
    //除了本类中定义的属性friendId,父类中的所有属性也要作为key-value添加到请求参数的dictionary当中
    return self.superclass;
}

如果后台定义的请求参数不符合iOS的风格,则可以对参数进行映射,比如,服务器定义的请求参数为friend_id,但我们知道iOS属性的命名风格是驼峰法,所以对应的属性名称我们希望是friendId,那么我们就需要对参数进行映射,这里的思路和YYModel的modelCustomPropertyMapper相似:

+ (NSDictionary *)propertysToDictionaryMapper{
    //属性friendId映射到请求参数的key为friend_id
    return @{@"friendId":@"friend_id"};
}

这样,属性friendId转换后的参数字典的key就是friend_id了。

可以增加父类的属性作为参数,自然就可以将某些属性从转换中剔除,如果不希望某些属性转化为请求参数,可以将这些属性添加到黑名单中,所有黑名单中的属性不参与转化:

+ (nullable NSArray<NSString *> *)propertyBlacklist{
    //这里表示,errorType这个属性不作为请求参数添加到参数dictionary中
    NSMutableArray *blackList = @[@"errorType"].mutableCopy;
    [blackList addObjectsFromArray:[super propertyBlacklist]];
    return blackList;
}

发起一次下载/上传请求


下载请求请使用LSYDownloadRequest,该类实现了断点续传功能。
上传请求请使用LSYUploadRequest,这个类只是简单对AFN的上传进行了封装。
这两个功能使用起来比较简单,不再过多说明。

小结


基础篇就到此结束了,下一篇是进阶篇

更多详细的内容,我写在了GithubDemo里,有兴趣的可以下载下来看看。

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

推荐阅读更多精彩内容