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<LXBaseRequestDelegate, LXBaseRequestParamDelegate, LXBaseRequestHeaderDelegate>

//调用接口需要的参数,可以从控制器赋值传过来
@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 () <LXBaseRequestCallBackDelegate>

//定义接口属性实现懒加载
@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<NSString *, NSString *> *)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

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

推荐阅读更多精彩内容

  • iOS网络架构讨论梳理整理中。。。 其实如果没有APIManager这一层是没法使用delegate的,毕竟多个单...
    yhtang阅读 5,098评论 1 23
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,566评论 25 707
  • AFHTTPRequestOperationManager 网络传输协议UDP、TCP、Http、Socket、X...
    Carden阅读 4,263评论 0 12
  • 致自己,愿不负时光也终不负自己
    李睿_阅读 120评论 0 0
  • 懂你,是一种简简单单的感动,懂得你的眼泪,给你最暖的安慰;深知你的憔悴,怪你为何不知疲惫。 懂你,是一种难言的柔情...
    叶样悠阅读 261评论 4 10