iOS OC项目优化之路(二)之Network篇

网络框架选型

AFNetworking

前后端交互是我们日常开发经常遇到的情况,在iOS中最知名的网络框架应该就是AFNetworking了。使用它可以让你从服务器很轻松的得到你所需要的数据,而不需要再去使用繁琐的NURLSession,它的用法如下:

[[AFHTTPSessionManager manager] GET:@"" parameters:@{} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

}failure:^(NSURLSessionDataTask*_Nullabletask,NSError*_Nonnullerror) {

}];

我们只需要传入url和para,就可以处理成功或失败的回调了。

AFNetworking的问题

对于AFHTTPSessionManager的处理,一般是保存一个全局的单例。但是这样的话会造成一个问题:

  1. 修改单个请求属性会影响到全局属性的修改,如过期时间。
  2. 无法获取到当前请求的状态,如正在请求中、请求失败或是成功。

XPNetworking

XPNetworking是我为了解决AFNetworking的这两个问题而写的基于面向对象的网络请求框架,它不但可以方便的自定义每一个请求,还基于YYCache的封装扩展了一个缓存的功能。它的使用方法如下:

    XPBaseRequest *request = [XPBaseRequest new];
    request.route = @"xx";
    request.path = @"xx";
    request.baseUrl = @"http://www.xx.com:8080";
    request.requestTimeOut = 10;
    [request startCompletionBlockWithSuccess:^(XPBaseRequest * _Nonnull request) {
        
    } failure:^(XPBaseRequest * _Nonnull request) {
        
    }];

针对网络缓存,它提供了两种缓存方式:磁盘缓存和内存缓存,它所支持的过期模式是存储日期过后的N秒时间。设置方法如下:

    request.diskCacheTime = 100;
    request.cacheType = XPCacheDisk | XPCacheMemory;
    request.memoryCacheTime = 100;
优点
  • 面向对象,可以通过基类定制划分业务接口
  • 请求对象高度自定制
  • 保留请求状态,可方便进行页面数据再处理
  • 集成缓存
    ①可方便定制缓存策略
    ②异步获取缓存,高并发不会阻塞主线程
    ③自动清理过期缓存
  • 同时间相同请求只会处理一条,但返回结果都会返回
  • 支持多请求结果同时处理以及多请求同步进行
  • 支持上传、断点续传

实现原理

XPNetworking库的目录结构是这样的:

  • XPBaseRequest,提供请求基本信息,状态的保存,对返回数据的处理
  • XPChainRequest,同步请求
  • XPBatchRequest,并发请求
  • XPRequestNetworkManager,发起网络请求的类,用来管理XPRequestUniquePackage对象
  • XPRequestUniquePackage,网络请求管理队列,用来处理同时进行多个相同请求的情况
  • XPRequestSessionManager,执行网络请求的类
  • XPRequestConfiguration,全局配置的类

主要从两个方面说起,一个是请求,另一个是缓存。

缓存

之所以将缓存封装进网络请求框架是因为iOS许多页面都是需要缓存一些数据,以便用户在进入app可以看到页面上的内容,而不是空白页面,优化用户体验。在之前的做法中,缓存与网络请求是分开的,在请求之前先取数据,然后再去缓存,存储的数据错综复杂,很有可能处理逻辑与从网络请求获取的数据处理逻辑不相同,这样就造成了代码的冗余,以及许多不必要的麻烦。

这里的缓存框架用的是YYCache,他是一个基于sqlite的高效缓存框架。它可以让我们不需要操作数据库就可以达到缓存数据的效果。但在使用过程中我发现有两点问题:

  1. 它的缓存模式并不是我需要的,它的缓存过期时间是基于使用时间,而我需要的是基于缓存时间。
  2. 它开启了wal,会在一定的情况下造成app占用内存过高。

所以针对这两个问题,我封装了一个管理YYCache的工具:
XPCache。

它的API基本与YYCache相同。它实现缓存过期机制实际上就是在YYCache中预留一个字段用来保存其他数据的缓存设置:

     NSString *cacheFolder = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
    NSString *path = [cacheFolder stringByAppendingPathComponent:XPCachePathName];
    _diskCache = [[YYDiskCache alloc] initWithPath:path];
    _memoryCache = [YYMemoryCache new];
    _memoryCache.name = XPCachePathName;
    _cacheManager =  [XPCacheManager new];
    id data =  [_diskCache objectForKey:XPCacheManagerName];
    if ([data isKindOfClass:[NSDictionary class]]) {
        _cacheManager.diskData = [NSMutableDictionary dictionaryWithDictionary:data];
    }else{
        [_diskCache removeAllObjects];
    }
    _cacheManager.delegate = self;
    [_cacheManager start];

关于频繁调用造成内存过大的问题是通过在操作YYCache时,先进行自身一套机制处理,然后再去操作YYCache。这样就减少了YYCache的高频操作。

网络请求

BaseRequest

网络请求是基于面向对象的,在这里有一个BaseRequest,它包含的是一个网络请求最基本的一些元素:

一、发起请求
/**
 请求host
 */
@property (nonatomic, copy) NSString * baseUrl;

/**
 首路径
 拼接: baseUrl + route + path
 */
@property (nonatomic, copy) NSString * route;

/**
 路径
 */
@property (nonatomic, copy) NSString * path;

/**
 参数
 */
@property (nonatomic, strong) NSMutableDictionary * params;

/**
 请求方法
 */
@property (nonatomic, assign) XPRequestMethod  method;

在发起请求后,会根据设置的request来设置请求头和响应,在没有设置request的属性时,所使用的是全局属性。如下

- (AFHTTPRequestSerializer *)configRequestSerializerByRequset:(XPBaseRequest *)request{
    AFHTTPRequestSerializer * serializer = [self requestSerializerWithRequestSerializerType:request.requestSerializerType];
    NSArray *requestAuthorizationHeaderFieldArray = request.requestAuthorizationHeaderFieldArray;
    if (request.requestAuthorizationHeaderFieldArray) {
        [serializer setAuthorizationHeaderFieldWithUsername:[requestAuthorizationHeaderFieldArray firstObject] password:[requestAuthorizationHeaderFieldArray lastObject]];
    }
    serializer.allowsCellularAccess = request.allowsCellularAccess;
    NSDictionary *headerFieldValueDictionary = request.headerFieldValueDictionary?request.headerFieldValueDictionary:self.configuration.headerFieldValueDictionary;
    if (headerFieldValueDictionary) {
        for (NSString * httpHeaderField in headerFieldValueDictionary.allKeys) {
            NSString *value = [request.headerFieldValueDictionary objectForKey:httpHeaderField];
            [serializer setValue:value forHTTPHeaderField:httpHeaderField];
        }
    }
    serializer.timeoutInterval = request.requestTimeOut == 0?self.configuration.timeOut:request.requestTimeOut;
    self.manager.requestSerializer = serializer;
    if (request.securityPolicy) {
        self.manager.securityPolicy = request.securityPolicy;
    }else{
        self.manager.securityPolicy = self.configuration.securityPolicy;
    }
    return serializer;
}
- (AFHTTPResponseSerializer *)confiResponseSerializerByRequest:(XPBaseRequest *)request{
    XPResponseSerializerType type = request.responseSerializerType;
    switch (type) {
        case XPResponseSerializerTypeHTTP:
            
            return self.httpResponseSerializer;
            break;
        case XPResponseSerializerTypeJSON:{
            NSSet *set = [request.acceptableContentTypes allObjects].count > 0?request.acceptableContentTypes : self.acceptableContentTypes;
            self.jsonResponseSerializer.acceptableContentTypes = set;
            return self.jsonResponseSerializer;
            break;
        }
        case XPResponseSerializerTypeXMLParser:{
            NSSet *set = [request.acceptableContentTypes allObjects].count > 0?request.acceptableContentTypes : self.acceptableContentTypes;
            self.xmlResponseSerializer.acceptableContentTypes = set;
            return self.xmlResponseSerializer;
            break;
        }
        default:
            break;
    }
}
二、请求状态

然后用这个请求头发起请求。同时在发起请求的时候给定请求状态,一共有这几种:

typedef NS_ENUM(NSInteger, XPRequestRunningStatus) {
    XPRequestRunningStatus_unStart = 0,//未开始
    XPRequestRunningStatus_running,//请求中-单
    XPRequestRunningStatus_finish_Success,//请求成功
    XPRequestRunningStatus_finish_Failure,//请求失败
    XPRequestRunningStatus_finish_Cancel,
    
};

这是request对象所包含的状态属性:

/**
 运行状态
 */
@property (nonatomic, assign, readonly) XPRequestRunningStatus  runningStaus;
/**
 是否取消
 */
@property (nonatomic, assign, readonly) BOOL  isCancelled;

/**
 是否运行中
 */
@property (nonatomic, assign, readonly) BOOL  isExecuting;

/**
 是否已经结束
 */
@property (nonatomic, assign, readonly) BOOL  isFinshed;

@property (nonatomic, assign, readonly) BOOL  isUnstart;
/**
 是否是请求失败
 */
@property (nonatomic, assign, readonly) BOOL  isFail;
三、同一请求同时发起时的合并

XPRequestUniquePackage类主要用来处理请求合并,当一个请求在发起到完成这段时间如果又发起一个相同的请求时,这个时候其实是没必要再进行网络请求的,这个时候可以把不需要发起的请求放入到队列中,在上一个请求完成后同时处理。

四、request对象自定制返回规则

相同的请求在处理时有可能会处理逻辑的测重点可能不同,request提供了处理自身返回结果的预处理能力。

/**
 json数据是否合法
 */
- (BOOL)validateJson;

在网络请求回调时会调用这个方法,在处理后返回YES则证明结果与预期相符,如果为NO则证明失败,会进入失败的回调。

五、请求的异步和同步

XPBatchRequest和XPChainRequest在内部处理了多请求的同步和异步。使用时只需要将请求加入到队列中,即可在回调中处理。

  XPBatchRequest *batch = [[XPBatchRequest alloc] initWithRequest:@[request]];
    [batch startCompletionBlockWithSuccess:^(XPBatchRequest *batchRequest) {
        
    }]; 

可在连获取到成功和失败的请求。

总结

面向对象的网络编程,可以让我们在与后端的交互中脉络更加清晰,减少资源的浪费,从而使我们的开发更加的高效。
感谢YTKNetwork提供的思路!

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,107评论 1 32
  • 概览 缓存组件应该说是每个客户端程序必备的核心组件,试想对于每个界面的访问都必须重新请求势必降低用户体验。但是如何...
    默默_David阅读 1,936评论 1 9
  • iOS网络架构讨论梳理整理中。。。 其实如果没有APIManager这一层是没法使用delegate的,毕竟多个单...
    yhtang阅读 5,205评论 1 23
  • 转载:https://blog.csdn.net/jason_chen13/article/details/519...
    chasitu阅读 1,425评论 0 6
  • 半晌贪欢梦醒,雨润廊前花影。 碎叶满秋千,急恐陌间风冷。 白兰,白兰,二月莫休春幸。
    立小笳阅读 203评论 2 5