iOS 网络请求模块封装 - OC

Swift的Alamofire使用久了之后,回头写OC的项目使用 -request:success:failure: 这种形式的网络请求却不太习惯了……于是动手封装一个基于AFNetworking3.1框架Alamofire风格的网络请求模块

代码github地址:ARKHTTPModule

基础功能

  • 请求的创建
  • 获取请求的状态过程
  • 请求的操作
  • 响应

这些基础功能的类为ARKRequest

1、请求的创建

通过请求地址、请求参数等创建一个网络请求实例

// 常用的HTTP请求,默认GET请求,请求超时时长15秒
+ (instancetype)requestWithURLString:(NSString *)URLStr
                          parameters:(nullable NSDictionary *)params;

+ (instancetype)requestWithMethod:(ARKRequestMethod)method
                       serializer:(ARKRequestSerializerType)serializer
                        URLString:(NSString *)URLStr
                       parameters:(nullable NSDictionary *)params
                   requestTimeout:(NSTimeInterval)timeout;

// 上传
+ (instancetype)uploadWithType:(ARKSessionType)type
                     URLString:(NSString *)URLStr
                    parameters:(nullable NSDictionary *)params
                      fromData:(nullable NSData *)bodyData;

// 下载
+ (instancetype)downloadWithType:(ARKSessionType)type
                       URLString:(NSString *)URLStr
                      parameters:(nullable NSDictionary *)params
                     destination:(nonnull DownloadDidFinishTargetBlock)destination;

// etc...

2、获取请求的状态过程

通过下列方法可以获取请求的进度、即将执行、即将暂停、即将取消、后台请求完毕状态

// 请求进度
- (ARKRequest *(^)(_Nullable ProgressBlock))requestProgressBlock;

// 请求即将执行
- (ARKRequest *(^)(_Nullable RequestStateBlock))requestWillResume;

// 请求即将暂停
- (ARKRequest *(^)(_Nullable RequestStateBlock))requestWillSuspend;

// 请求即将取消
- (ARKRequest *(^)(_Nullable RequestStateBlock))requestWillCancel;

// 后台请求完毕闭包
- (ARKRequest *(^)(_Nullable SessionDidFinishEventsForBackgroundURLSessionBlock))didFinishEventsForBackgroundURLSession;

3、请求的操作

对请求进行发起、暂停、取消等操作

// 发送请求
- (ARKRequest *(^)(void))resume;

// 暂停请求
- (ARKRequest *(^)(void))suspend;

// 取消请求
- (ARKRequest *(^)(void))cancel;

4、响应

这里参考了Alamofire的请求完毕回调形式
ARKRequest类中有一个NSOperationQueue串行队列,该队列在创建的时候处于暂停状态
用户在使用Response添加代码时,将Block代码放入这个队列中
当有响应时,顺序执行队列中的Block

// 返回NSData
- (ARKRequest *(^)(ResponseDataBlock))responseDataOnMainThread;

// 返回NSString
- (ARKRequest *(^)(ResponseStringBlock))responseStringOnMainThread;

// 返回JSON(NSArray或NSDictionary)
- (ARKRequest *(^)(ResponseJSONBlock))responseJSONOnMainThread;

// 返回JSON解析模型(目前默认是从JSON数据解析)
// 只要自定义的数据模型遵守< ARKMappableObject >协议
// 在返回JSON数据时,会根据传入的Class自动解析为对应的数据模型
- (ARKRequest *(^)(ResponseMappableObjectBlock, Class<ARKMappableObject>))responseMappableObjectOnMainThread;

// 返回JSON解析模型数组(目前默认是从JSON数据解析)
// 同上,解析JSON数据,返回数据模型数组
- (ARKRequest *(^)(ResponseMappableObjectArrayBlock, Class<ARKMappableObjectArray>))responseMappableObjectArrayOnMainThread;

// 解析协议
@protocol ARKMappableObject <NSObject>
+ (nullable id<ARKMappableObject>)objectWithResponseObject:(id)object;
@end

@protocol ARKMappableObjectArray <NSObject>
+ (nullable NSArray<id<ARKMappableObject>> *)arrayWithResponseObject:(id)object;
@end

请求例子

假设请求一个JSON数据

- (IBAction)startRequest:(UIButton *)sender {
  // 创建请求
  ARKRequest *request = [ARKRequest requestWithURLString:@"http://www.example.com/json" parameters:nil];

  // 如果self不持有request,那么block内的self可以不使用弱引用,因为block执行完毕后会清空
  // 相应的,执行该请求的对象(例如ViewController)生命周期将会被延长
  // 如果希望退出当前控制器时,控制器可以立即销毁,那么请在block里使用weak self
  request.requestWillResume(^(ARKRequest *request) {
    
      // 请求前的准备阶段,展示加载视图
      [self showLoadingView];
    
  }).requestProgressBlock(^(NSProgress *progress) {
    
      // 更新进度,requestProgressBlock不在主线程
      dispatch_async(dispatch_get_main_queue(), ^{
          self.progressView.progress = (float)progress.completedUnitCount / (float)progress.totalUnitCount;
      });
    
  }).responseJSONOnMainThread(^(NSURLSessionTask *task, id json, NSError *error) {
    
      // 隐藏加载视图
      [self hideLoadingView];
      NSLog(@"请求完毕, thread: %@", [NSThread currentThread]);
    
      if (error == nil) {
          // 请求成功
          [self requestSuccessWithJSON:json];
      } else {
          // 请求失败
          [self requestFailureWithError:error];
      }

  }).resume();
}

辅助类

另外还有两个辅助类:

  • ARKRequestCacheManager
  • ARKNonRepetitiveRequestManager

ARKRequestCacheManager用于缓存请求
ARKNonRepetitiveRequestManager用于发起不重复的请求

1、ARKRequestCacheManager(单例)

使用ARKRequestCacheManager发起网络请求时,manager会缓存请求实例
当请求完毕后,manager移除对应的请求实例
可以通过该辅助类轻松实现暂停和取消请求

如果是使用ARKRequestCacheManager发起的下载请求,当下载失败时,manager会从error.userInfo字典中,取出NSURLSessionDownloadTaskResumeData
并且使用URL作为Key缓存在NSCache中,同时还会保存在磁盘Cache目录下

当下次发起下载请求时,会根据URL判断本地是否有断点数据

  • 如果有断点数据,那么会调用 -downloadWithType:resumeData:destination: 进行断点续传
  • 如果没有断点数据,重新创建下载请求

使用ARKRequestCacheManager发起网络请求

- (void)startDownload {
    
    // 使用 ARKRequestCacheManager 发起下载请求
    // ARKRequestCacheManager 会保存这次请求,直到请求完毕
    // 当下载失败时,会保存这次的断点数据,下次发起时会优先读取断点数据进行断点续传

    ARKRequestCacheManager *manager = [ARKRequestCacheManager sharedManager];
    ARKRequest *request = [manager downloadWithType:ARKSessionTypeDefault 
                                          URLString:kURLString
                                         parameters:nil
                                        destination:^NSURL * _Nonnull(NSURL * _Nonnull location, NSURLResponse * _Nonnull response) {
        
        // 指定保存路径
        NSString *documentDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
        NSString *path = [documentDir stringByAppendingPathComponent:@"image.jpg"];

        // TODO: = =
        // 如果不是fileURL,那么最后response很可能会出错
        return [NSURL fileURLWithPath:path];
    }];
    
    
    __weak typeof(&*self) weakSelf = self;
    
    request.requestWillResume(^(ARKRequest *request) {
        
        // 显示加载视图
        [weakSelf showLoadingView];
        
    }).requestWillSuspend(^(ARKRequest *request) {
        
        NSLog(@"请求即将暂停,state: %ld", request.task.state);
        
    }).requestProgressBlock(^(NSProgress *progress) {
        
        // 更新进度条
        dispatch_async(dispatch_get_main_queue(), ^{
            weakSelf.progressView.progress = (float)progress.completedUnitCount / (float)progress.totalUnitCount;
        });
        
    }).responseDataOnMainThread(^(NSURLSessionTask *task, NSData *data, NSError *error) {
        
        // 隐藏加载视图
        [weakSelf hideLoadingView];
        
        NSLog(@"请求完毕, thread: %@", [NSThread currentThread]);
        
        if (error != nil) {
            [weakSelf showErrorMessage:[NSString stringWithFormat:@"下载失败, error:%@", error.localizedDescription]];
            return ;
        }
        
        if (data == nil || [data isKindOfClass:[NSNull class]]) {
            [weakSelf showErrorMessage:[NSString stringWithFormat:@"返回空数据"]];
            return ;
        }
        
        weakSelf.imageView.image = [UIImage imageWithData:data];

    }).resume();
}

// 暂停
- (IBAction)suspendDownload {
    // 从 ARKRequestCacheManager 取出请求
    ARKRequest *request = [[ARKRequestCacheManager sharedManager] requestCacheWithURLString:kURLString];
    if (request != nil) {
        request.suspend();
        [self hideLoadingView];
    }
}

// 执行
- (IBAction)resumeDownload {
    // 从 ARKRequestCacheManager 取出请求
    ARKRequest *request = [[ARKRequestCacheManager sharedManager] requestCacheWithURLString:kURLString];

    if (request != nil && (request.state == NSURLSessionTaskStateSuspended)) {
        // 继续请求
        request.resume()
        [self showLoadingView];
    } else {
        // 创建请求
        [self startDownload];
    }
}

2、ARKNonRepetitiveRequestManager(单例)

ARKNonRepetitiveRequestManager 同样具有 ARKRequestCacheManage r的功能,并且使用 ARKNonRepetitiveRequestManager 发起多个相同的请求时,只允许最先发起的请求生效,其余请求均忽略

使用ARKNonRepetitiveRequestManager发起网络请求

- (IBAction)backgroundDownload:(UIButton *)sender {
    ARKNonRepetitiveRequestManager *manager = [ARKNonRepetitiveRequestManager sharedManager];

    // 可以保证只有最先发起的请求有效,不进行重复的请求
    ARKRequest *request = [manager downloadWithType:ARKSessionTypeBackground // 后台下载
                                          URLString:kImageURLString
                                         parameters:nil
                                        destination:^NSURL * _Nonnull(NSURL * _Nonnull location, NSURLResponse * _Nonnull response) {
        
        NSString *documentDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
        NSString *path = [documentDir stringByAppendingPathComponent:@"BackgrounDownloadImage.jpg"];
        return [NSURL fileURLWithPath:path];
    }];
    
    __weak typeof(&*self) weakSelf = self;
    
    request.responseDataOnMainThread(^(NSURLSessionTask *task, NSData *data, NSError *error) {
        
        NSLog(@"下载完毕, thread: %@", [NSThread currentThread]);
        
        if (data.length > 0) {
            weakSelf.imageView.image = [UIImage imageWithData:data];
        }

    }).didFinishEventsForBackgroundURLSession(^(NSURLSession *session) {
        NSLog(@"后台下载完毕");
        
        AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
        void(^completionHandler)() = delegate.didFinishEventsForBackgroundURLSession;
        delegate.didFinishEventsForBackgroundURLSession = nil;
        
        // 通知系统已接收到后台事件,清除后台网络会话标识
        completionHandler();
        
    }).resume();
}

其他

目前OC的框架中网络层仍然是 -request:success:failure:的调用形式(毕竟成熟,调用形式更普及 = =)
这个网络模块的封装纯属一时兴起,还有很多问题没有考虑,例如安全机制问题等等
总之就是有待完善

欢迎大家提出建议

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,060评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,761评论 25 707
  • 1.嵌入相关配置 1)unity导出的工程与已有工程置于相同根路径下。 2)在已有工程引用三个文件夹,Data 选...
    夜锦凉丶阅读 12,321评论 53 22
  • Android基础知识 Android 的四大组件是哪些? Activity,Service,Broadcast和...
    沉思的Panda阅读 1,947评论 3 33
  • 我像个正人君子一样的开始叙述,在那一年的冬天 爱上一个如天蓝般的女孩。 章一:开学季 泪别青春最苦最美好的岁月。 ...
    Penn先生阅读 443评论 2 4