美文网首页iOS点点滴滴
LSYNetworking:基于AFNetworking的网络请

LSYNetworking:基于AFNetworking的网络请

作者: 樂幽 | 来源:发表于2023-06-19 20:58 被阅读0次

给大家分享一下我封装的一个基于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{
    if ([resultClass isSubclassOfClass:NSDictionary.class] ||
        [resultClass isSubclassOfClass:NSArray.class] ||
        [resultClass isSubclassOfClass:NSString.class] ||
        [resultClass isSubclassOfClass:NSNumber.class]) {
        //如果resultClass是以上四种类型,则不需要转model,只有自定义模型需要转化
        return resultJson;
    }
    return [resultClass yy_modelWithJSON:resultJson];
}

- (NSArray *)modelArrayWithClass:(Class)elementClass json:(id)resultJson{
    //这里可以断言下类型是否匹配,或者可以做一下判断,如果不匹配,则直接返回resultJson/返回空
    NSAssert([resultJson isKindOfClass:NSArray.class], @"Result class error,result json object is not a kind of NSArray!");
    return [NSArray yy_modelArrayWithClass:elementClass json:resultJson];
}

- (NSDictionary *)modelDictionaryWithClass:(Class)elementClass json:(id)resultJson{
    //这里可以断言下类型是否匹配,或者可以做一下判断,如果不匹配,则直接返回resultJson/返回空
    NSAssert([resultJson isKindOfClass:NSDictionary.class], @"Result class error,result json object is not a kind of NSDictionary!");
    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,如果需要GET请求,则需要将usePost属性设置为NO

接下来,我们创建一个映射的模型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里,有兴趣的可以下载下来看看。

相关文章

网友评论

    本文标题:LSYNetworking:基于AFNetworking的网络请

    本文链接:https://www.haomeiwen.com/subject/mgzcydtx.html