美文网首页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