美文网首页
iOS 网络请求模块封装 - OC

iOS 网络请求模块封装 - OC

作者: arKenLee | 来源:发表于2016-07-18 23:32 被阅读0次

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:的调用形式(毕竟成熟,调用形式更普及 = =)
这个网络模块的封装纯属一时兴起,还有很多问题没有考虑,例如安全机制问题等等
总之就是有待完善

欢迎大家提出建议

相关文章

网友评论

      本文标题:iOS 网络请求模块封装 - OC

      本文链接:https://www.haomeiwen.com/subject/llcmjttx.html