LXNetwork – 基于AF3.0封装的iOS网络请求库

本框架实现思路与YTKNetwork和RTNetworking类似,相当于一个简单版,把每一个网络请求封装成对象。使用LXNetwork,你的每一个请求都需要继承LXBaseRequest类,通过覆盖父类的一些方法或者实现相关协议方法来构造指定的网络请求。这个网络库可直接在项目中使用,但是有些功能完成度不是很完美,待完善。

GitHud地址:https://github.com/CoderLXWang/LXNetwork



一、为什么要这样做?

实现思路的图在下面,可以对比着图看下面内容。

直接封装一个简易的HttpTool,里面直接调用AF,返回responseObject直接返回, 这样不行吗, 为什么要弄这么麻烦?

显而易见的优点大概有以下几点:

1,前后隔离AFNetworking,以后如果升级AF或者替换其他框架, 只需要改动直接与AF接触的LXRequestProxy和LXResponse内的代码即可,避免对项目中业务代码产生影响(半小时完成从AF2.6升级AF3.0,重度使用的三方框架一般都要隔离一下)

2,将每个接口抽象成一个类,易于管理,按每个接口的需求构造请求(比如有的接口要缓存,有的接口不要缓存)

3,所有接口调用都经过LXBaseRequest,可以方便的在基类中处理公共逻辑(比如项目全部完成了,突然要用请求参数排序,加盐等方式加密)

缺点:使用麻烦。。。。。

实现思路.png

二、思路讲解

包括缓存在内的大体思路即上图,上图中箭头颜色由浅到深即为调用顺序,大概讲解一下

1,首先要把网络请求封装成对象,即图中TestApi(继承于LXBaseRequest),在Viewcontroller中调用接口loadData

2,这时会调用到TestApi的父类LXBaseRequest中的loadData方法, 并从TestApi实现的重写或者协议方法中获取url, 请求类型, 参数等信息, 调用LXRequestProxy中的请求方法

3, LXRequestProxy内部调用AF的GET或其他方法

4, 回调之后并没有直接返回responseObject,而是转换成LXResponse,这样返回的数据经过封装, 相当于从后面也进行了隔离, 比如AF2.x的时候回调block的参数还是^(AFHTTPRequestOperation *operation, id reponseObject),AF3.x就变成了^(NSURLSessionDataTask *task, id reponseObject),如果不转换一下, 直接返回到控制器,改起来就尴尬了。。。

5,一路回调到TestApi, 再到ViewController

6,走完之后再看一下缓存如何处理,首先,缓存一定分为存和取,

存,在第5步, 一路回调到父类中的successCallApi这一步,将回调数据存起来的(用GET+登录状态或其他+url+参数转换的字符串作为key,这个随意,适合项目即可)。

取,在第2步调用父类LXBaseRequest中的loadData时会先检测该接口对应的数据是否存在, 存在直接返回父类LXBaseRequest中的successCallApi,不存在则正常发出请求



三, 举个栗子

接口如何定义?

FirstTestApi.h

#import "LXBaseRequest.h"

//要遵守什么协议取决于这个接口需要如何构造, LXBaseRequestDelegate一定要遵守,用于获取url等基本信息

@interface FirstTestApi : LXBaseRequest

//调用接口需要的参数,可以从控制器赋值传过来

@property (nonatomic, assign) int tp;

/** 是否为读取新数据(对应下拉刷新) */

@property (nonatomic, assign) BOOL isLoadNew;

/** 是否为最后一页 */

@property (nonatomic, assign) BOOL isLastPage;

/** 可以是转好的数据模型,这里只是示意一下 */

@property (nonatomic, strong) id dataModel;

@property (nonatomic, assign) NSUInteger dataLength;

@end

FirstTestApi.m

#import "FirstTestApi.h"

//基本url,可定义成全局变量或者宏, 拼接URL使用

#define BaseUrl @"http://api.zsreader.com/v2/"

@implementation FirstTestApi

{

int page;//记录页码值

int pageSize;//每页条数

NSInteger total;//记录总条数

}

//重写init方法是为了设置获取参数和请求头的代理, LXBaseRequestDelegate不用设置是因为已在父类中设置好, 如果只需要最基本的LXBaseRequestDelegate则不需要重写init

- (instancetype)init

{

self = [super init];

if (self) {

self.paramSource = self;

self.headerSource = self;

}

return self;

}

- (LXBaseRequestType)requestType {

return LXBaseRequestTypeGet;

}

//1. 如果是POST请求下面两个方法都不用写

//2. 如果接口需要缓存, 这个可以不写,默认需要

//3. 某些GET接口, 不需要缓存一定要写, 比如需要数据及时变化的

- (BOOL)shouldCache {

return YES;

}

//1. 删除缓存的时候要用来拼接缓存的key, 所以如果使用了缓存, 这个方法最好要写, 简单点就是直接返回requestUrl, 复杂一点的情况, 写各种不同的url的共同部分, 比如一个接口类里面有两个相近的url,@"pub/home/2"和@"pub/found/2", 则可以写@"pub/", 区共同部分,清缓存时都清掉

//2. 不能引用当前类的变量, 直接崩掉, self.的东西都不行

//3. 如果有不同情况, (@"情况1"|@"情况2"), 比如 @"pub/home" 和 @"pub/found" 可以写[NSString stringWithFormat:@"%@(%@|%@)", BaseUrl, @"pub/home", @"pub/found"];

- (NSString *)cacheRegexKey {

return [NSString stringWithFormat:@"%@%@", BaseUrl, @"pub/home/2"];

}

- (NSString *)requestUrl {

return [NSString stringWithFormat:@"%@%@", BaseUrl, @"pub/home/2"];

}

//伪代码, 本接口不需要header, 演示一下

- (NSDictionary *)headersForRequest:(LXBaseRequest *)request {

//    if (app.isLogin) {

//        return @{@"token":app.token};

//    }

return nil;

}

- (NSDictionary *)paramsForRequest:(LXBaseRequest *)request {

//假如是上拉刷新,取下一页

NSMutableDictionary *params = [NSMutableDictionary dictionary];

if (!self.isLoadNew) {

params[@"page"]=@(page);

}

params[@"tp"] = @(self.tp);

return [params copy];

}

//在调用API之前额外添加一些参数,但不应该在这个函数里面修改已有的参数

//如果实现这个方法, 一定要在传入的params基础上修改 , 再返回修改后的params

- (NSDictionary *)reformParams:(NSDictionary *)params {

NSMutableDictionary *mParams = [params mutableCopy];

[mParams setObject:@"test" forKey:@"test"];

return [mParams copy];

}

//获取到包装之后的LXResponse类型的返回数据,先处理一下,比如讲数据转成模型,供控制器回调使用

-(void)beforePerformSuccessWithResponse:(LXResponse *)response

{

[super beforePerformSuccessWithResponse:response];

if(self.isLoadNew){

page=1;

pageSize = [response.content[@"count"] intValue];

total = [response.content[@"total"] integerValue];

}

self.isLastPage=(total<=page*pageSize);

//可以在这转好模型, 控制器里直接用

self.dataModel = response.result;

self.dataLength = response.responseData.length;

page++;

}

@end

控制器中如何使用?

ViewController.h

#import "ViewController.h"

#import "FirstTestApi.h"

//遵守回调协议LXBaseRequestCallBackDelegate

@interface ViewController ()

//定义接口属性实现懒加载

@property (nonatomic, strong) FirstTestApi *firstApi;

@end

@implementation ViewController

#pragma mark --------- 懒加载 --------

- (FirstTestApi *)firstApi

{

if (!_firstApi) {

_firstApi = [[FirstTestApi alloc] init];

_firstApi.delegate = self;

_firstApi.tp = 1;

}

return _firstApi;

}

- (void)viewDidLoad {

[super viewDidLoad];

//添加点击调用接口的按钮

[self configBtns];

}

//firstApi调用刷新, 即page = 1

- (void)loadNew {

self.firstApi.isLoadNew=YES;

[self.firstApi loadData];

}

//firstApi调用加载更多, 即page++

- (void)loadMore {

self.firstApi.isLoadNew=NO;

[self.firstApi loadData];

}

#pragma mark --------- LXBaseRequestCallBackDelegate --------

- (void)requestDidSuccess:(LXBaseRequest *)request {

if ([request isKindOfClass:[FirstTestApi class]]) {

FirstTestApi *testApi = (FirstTestApi *)request;

NSLog(@"接口1请求成功 %lu ", testApi.dataLength);

}

}

- (void)requestDidFailed:(LXBaseRequest *)request {

NSLog(@"请求失败");

}

@end



四,还有那些功能及注意

1,如何清除缓存, 比如在A界面的某个操作导致B,C界面数据都应发生变化, 这时切换到B,C界面(缓存有效时间内,默认30秒,可在LXNetworkConfiguration中设置),如果还是使用缓存就不对了。

方式1: 如果在需要清缓存的时刻能获取到BC界面相应接口, 可调用[B.firstApi deleteCache],接口父类LXBaseRequest的deleteCache方法即可清除缓存

方式2: 一般情况下, 在A界面操作的时候可能已经获取不到BC界面的接口了,这是可以通过通知清除,让AppDelegate监听清除缓存的通知

#import "AppDelegate.h"

#import "LXNetworkConfiguration.h"

#import "LXCache.h"

@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInvalidCache:) name:LXDeleteCacheNotification object:nil];

return YES;

}

-(void)handleInvalidCache:(NSNotification*) notify

{

NSDictionary* dict = notify.userInfo;

NSArray* arr = [dict objectForKey:LXDeleteCacheKey];

for (Class cls in arr) {

[[LXCache sharedInstance] deleteCacheWithClass:cls];

}

}

A界面某个操作成功之后

[[NSNotificationCenter defaultCenter] postNotificationName:LXDeleteCacheNotification

object:nil

userInfo:@{LXDeleteCacheKey

: @[NSClassFromString(@"TestApi"),

NSClassFromString(@"xxxApi"),

NSClassFromString(@"xxxApi"),

]}];

2,如果一个界面内有多个api怎么办, 都在requestDidSuccess里通过类型写if else 很乱, 实现requestDicWithClassStrAndSELStr将回调分发到单独的方法中

#pragma mark --------- LXBaseRequestCallBackDelegate --------

//@required方法, 实现一下, 不用写东西

- (void)requestDidSuccess:(LXBaseRequest *)request {

}

//如果一个控制器内存在多个接口,并使用协议代理方式回调,则可以实现下面方法,将各个请求回调分发到各自的方法里,字典key为接口类名,value为方法的selector字符串

- (NSDictionary *)requestDicWithClassStrAndSELStr {

NSMutableDictionary *dic = [NSMutableDictionary dictionary];

[dic setObject:NSStringFromSelector(@selector(handleFirstTestApi:)) forKey:@"FirstTestApi"];

[dic setObject:NSStringFromSelector(@selector(handleSecondTestApi:)) forKey:@"SecondTestApi"];

return [dic copy];

}

- (void)handleFirstTestApi:(FirstTestApi *)api {

NSLog(@"接口1请求成功 %lu ", api.dataLength);

}

- (void)handleSecondTestApi:(SecondTestApi *)api {

NSLog(@"接口2请求成功 %@ ", api.dataModel);

}

- (void)requestDidFailed:(LXBaseRequest *)request {

NSLog(@"请求失败");

}

3,如何使用block回调,注意使用这种方式回调不用遵守LXBaseRequestCallBackDelegate协议和设置self.secondApi.delegate = self

- (void)loadSecondApi {

[self.secondApi loadDataWithSuccess:^(LXBaseRequest *request) {

SecondTestApi *testApi = (SecondTestApi *)request;

NSLog(@"block 接口2请求成功 %@ ", testApi.dataModel);

} fail:^(LXBaseRequest *request) {

NSLog(@"接口2请求失败");

}];

}

4,接口有动态加密的缓存处理, 如果接口有加密, 并且融合了时间戳等,每次调用的签名都不一致的话,由于缓存的key拼接了参数, 如果把动态变化的签名也拼进去就永远找不到缓存,那么就需要修改以下代码, 将下面代码中注释部分打开, 在if条件中排除会动态变化的参数的key

NSDictionary+LXNetworkParams.m

#import "NSDictionary+LXNetworkParams.h"

#import "NSArray+LXNetworkParams.h"

@implementation NSDictionary (LXNetworkParams)

/** params 转换为NSString */

- (NSString *)lxUrlParamsToString {

//字典排序

NSArray *sortedArray = [self lxUrlParamsToArray];

//数组生成字符串

return [sortedArray lxUrlParamArrayToString];

}

- (NSArray *)lxUrlParamsToArray {

NSMutableArray *result = [[NSMutableArray alloc] init];

[self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {

if (![obj isKindOfClass:[NSString class]]) {

obj = [NSString stringWithFormat:@"%@", obj];

}

obj = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,  (CFStringRef)obj,  NULL,  (CFStringRef)@"!*‘();:@&;=+$,/?%#[]",  kCFStringEncodingUTF8));

//        if ([obj length] > 0 && (![key isEqualToString:TIMESTAMP_KEY] && ![key isEqualToString:SIGNATURE_KEY]) ) {

[result addObject:[NSString stringWithFormat:@"%@=%@", key, obj]];

//        }

}];

NSArray *sortedResult = [result sortedArrayUsingSelector:@selector(compare:)];

return sortedResult;

}

@end

5,其他功能用法见源码,有问题或者建议欢迎交流沟通



GitHud地址:https://github.com/CoderLXWang/LXNetwork

时间: 2024-10-10 13:16:30

LXNetwork – 基于AF3.0封装的iOS网络请求库的相关文章

造轮子 | 怎样设计一个面向协议的 iOS 网络请求库

近期开源了一个面向协议设计的网络请求库 MBNetwork,基于 Alamofire 和 ObjectMapper 实现,目的是简化业务层的网络请求操作. 须要干些啥 对于大部分 App 而言,业务层做一次网络请求通常关心的问题有例如以下几个: 怎样在任何位置发起网络请求. 表单创建. 包括请求地址.请求方式(GET/POST/--).请求头等-- 载入遮罩. 目的是堵塞 UI 交互,同一时候告知用户操作正在进行. 比方提交表单时在提交按钮上显示 "菊花",同一时候使其失效. 载入进度

造轮子 | 如何设计一个面向协议的 iOS 网络请求库

最近开源了一个面向协议设计的网络请求库 MBNetwork,基于 Alamofire 和 ObjectMapper 实现,目的是简化业务层的网络请求操作. 需要干些啥 对于大部分 App 而言,业务层做一次网络请求通常关心的问题有如下几个: 如何在任意位置发起网络请求. 表单创建.包含请求地址.请求方式(GET/POST/--).请求头等-- 加载遮罩.目的是阻塞 UI 交互,同时告知用户操作正在进行.比如提交表单时在提交按钮上显示 "菊花",同时使其失效. 加载进度展示.下载上传图片

自己动手写一个iOS 网络请求库的三部曲[转]

代码示例:https://github.com/johnlui/Swift-On-iOS/blob/master/BuildYourHTTPRequestLibrary 开源项目:Pitaya,适合大文件上传的 HTTP 请求库:https://github.com/johnlui/Pitaya 本系列文章中,我们将尝试使用 NSURLSession 技术构建一个自己的网络请求库. NSURLSession 简介 NSURLSession 是 iOS7 引入的新网络请求接口,在 WWDC2013

android基于开源网络框架asychhttpclient,二次封装为通用网络请求组件

网络请求是全部App都不可缺少的功能,假设每次开发都重写一次网络请求或者将曾经的代码拷贝到新的App中,不是非常合理,出于此目的,我希望将整个网络请求框架独立出来,与业务逻辑分隔开,这样就能够避免每次都要又一次编写网络请求,于是基于我比較熟悉的asynchttpclient又一次二次封装了一个网络请求框架. 思路:网络请求层唯一的功能就是发送请求,接收响应数据,请求取消,cookie处理这几个功能,二次助封装后这些功能能够直接调用封装好的方法就可以. 二次助封装代码例如以下: 1.功能接口: /

基于Retrofit+RxJava的Android分层网络请求框架

目前已经有不少Android客户端在使用Retrofit+RxJava实现网络请求了,相比于xUtils,Volley等网络访问框架,其具有网络访问效率高(基于OkHttp).内存占用少.代码量小以及数据传输安全性高等特点. Retrofit源码更是经典的设计模式教程,笔者已在之前的文章中分享过自己的一些体会,有兴趣的话可点击以下链接了解:<Retrofit源码设计模式解析(上)>.<Retrofit源码设计模式解析(下)> 但在具体业务场景下,比如涉及到多种网络请求(GET/PU

iOS网络请求框架:MKNetWorkKit的使用

MKNetWorkKit是由一个印度小伙子写的,是用于网络请求的库,支持ARC,我fork了一下,代码的网址这里给出. 本人fork作者代码地址(DoubleYi):https://github.com/DoubleYi/MKNetworkKit 作者源码地址(MugunthKumar):https://github.com/MugunthKumar/MKNetworkKit 作者关于类库介绍的地址(MugunthKumar):http://blog.mugunthkumar.com/produ

Flutter中的单例以及网络请求库的封装

https://zhuanlan.zhihu.com/p/53498914 Flutter中的单例以及网络请求库的封装 ClassNotFound 程序员 25 人赞同了该文章 Why?为什么需要单例 在Android中我们经常使用OkHttp来进行网络请求,但我们并不希望每次都创建一个OkHttpClient:亦或有些资源初始化非常麻烦,消耗性能,我们希望一次创建,处处使用.这时候就需要单例.Dio作为flutter中的OkHttp,我们也可以用单例模式对其进行封装. How?如何用dart实

ios网络请求3840错误的问题

ios网络请求3840错误的问题 by 伍雪颖 Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn't be completed. (Cocoa error 3840.)" (Unterminated string around character 11080.) UserInfo=0x171065d00 {NSDebugDescription=Unterminated string around charac

Android 各大网络请求库的比较及实战,android请求库实战

自己学习android也有一段时间了,在实际开发中,频繁的接触网络请求,而网络请求的方式很多,最常见的那么几个也就那么几个.本篇文章对常见的网络请求库进行一个总结. HttpUrlConnection 最开始学android的时候用的网络请求是HttpUrlConnection,当时很多东西还不知道,但是在android 2.2及以下版本中HttpUrlConnection存在着一些bug,所以建议在android 2.3以后使用HttpUrlConnection,之前使用HttpClient.