美文网首页networknetworkiOS技巧学习_JM
用AFNetworking3.0封装网络请求

用AFNetworking3.0封装网络请求

作者: cbsfly_ | 来源:发表于2016-01-15 11:23 被阅读15467次

    前言

    由于之前一直是用别人封装好的网络请求,一来版本太旧,二来觉得太臃肿很多功能代码不知道是干嘛的,所以想尝试自己封装一个网络请求,可能比较简单比较入门,很多问题肯定考虑不周,而且网上关于AFNetworking3.0库的教程也不多,所以大家当成3.0的小教程就好。

    配置AFNetworking环境

    AFNetworking更新了3.0,之前的工程一直用的2.63。这两个版本变化还是比较大的,以前的AFNetworking是NSURLConnection + NSOperation,但是3.0版本移除了对NSURLConnectionOperation的支持,这意味着之前版本的“主力军”被移除了,所以若是原封不动的用之前工程网络请求部分的代码一般都会报错,所以大家都要尝试用新版的AFNetworking来更新自己的网络请求了。取消了NSURLConnectionOperation,AFNetworking是用iOS7中新的网络接口NSURLSessionOperation来代替的。NSURLSessionOperation在程序进入后台后会比NSURLConnectionOperation更加灵活,更先进。

    为了使用AFNetworking3.0,还有一个比较坑爹的是你必须使用Xcode7以上的版本。因为AFNetworking3.0 使用了nullable新特性,而低版本的Xcode并不识别,所以用低版本的Xcode运行3.0会报错。更坑爹的是要用Xcode7还要更新你OS X到EI Capitan版本,光更新这两个我就花了一天的时间(其实笔者下载了两次Xcode7才发现要先更新操作系统才行)。更新操作系统踩坑点这里

    有了Xcode7,就可以用cococapods下载配置最新的AFNetworking了。

    platform :ios, '8.0'
    pod 'AFNetworking', '~> 3.0'
    

    坑爹的事还有,在iOS 9中 苹果将原http协议改成了https协议 使用 TLS1.2 SSL加密请求数据,所以我们不能直接请求http协议下的数据了。

    解决办法也很简单,用编辑器打开工程文件下的Info.plist文件添加下面的代码就可以了

    <key>NSAppTransportSecurity</key>  
        <dict>  
          <key>NSAllowsArbitraryLoads</key><true/>  
        </dict>  
    

    stackoverflow的回答点这里

    这样一来我们就配置好环境了。

    使用AFNetworking

    很多人对AFNetworking都不陌生,这一段可能有点老生常谈的感觉。

    用AFNetworking第一步都是一样的,即创建一个manager。关于self-manager的扩展可以看这篇文章学习一下。

    在AFNetworking中创建manager的方法是这样的

    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    

    在AppDelegate中你需要开启你的网络监听器,这样AFStringFromNetworkReachabilityStatus属性就有了值显示你的网络状态。

    [[AFNetworkReachabilityManager sharedManager] startMonitoring];
    

    如果你需要上传或者下载数据,那么还需要创建一个NSURLSessionConfiguration类,本文只讲述普通的请求数据,所以先不涉及NSURLSessionConfiguration。

    请求数据大部分情况下就是像服务器发送GET或者POST请求,然后服务器会返回你一串JSON格式的数据,你需要解析这段JSON数据然后呈现在你的UI上。

    GET - 从指定的资源请求数据。

    POST - 向指定的资源提交要被处理的数据

    AFNetworking请求数据的代码也很简单。

    [manager GET:@"http://httpbin.org/get" parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
        NSLog(@"JSON: %@", responseObject);
    } failure:^(NSURLSessionTask *operation, NSError *error) {
        NSLog(@"Error: %@", error);
    }];
    

    这段代码非常易懂,这也是AFNetworking的强大之处。parameters:后面跟着的是你需要给后台传送的参数。而id responseObject是后台返给你的数据。我输出了一下[responseObject class]发现responseObject是一个NSDictionary,那么以后在字典中用的方法也适用于responseObject,后面的代码中我也做了修改。

    这样我们就成功请求到了数据。接下来要做的就是封装了。

    网络请求的封装

    记得学习软件工程的时候我们老师给我们反复强调了代码复用的重要性,我当时也深受启发。而网络请求在我们的应用开发中我觉得是最常用的需要代码复用的部分,因为他使用次数多,且代码都差不多,不受别的模块代码干扰,即低耦合,高内聚。

    上文提到了我们进行网络请求要先创建一个manager,就是说每次请求我们都需要创建manager。那么我们先对这一步进行封装。

    先创建一个新的继承于AFHTTPSessionManager的类,我这里就叫cbsNetWork了。在我的封装中,一开始我是先创建一个类方法。

    + (instancetype)sharedManager {
    static cbsNetWork *manager = nil;
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        manager = [[self alloc] initWithBaseURL:[NSURL URLWithString:@"http://httpbin.org/"]];
    });
    return manager;
    }
    

    这是每次执行网络请求库的时候都要执行的方法。在这个方法中,其实就是初始化了一个manager,但与一般的创建不同,这里用到了dispatch_once也就是iOS ARC中创建单例模式的方法。为何这里要用到单例模式?因为这个manager应该是系统中唯一的实例。而dispatch_once中的方法不管多少次调用都只会运行一次。更多的关于单例模式点这个网站吧。

    接下来就是重写父类的初始化方法initWithBaseURL。在父类(也就是AFNetworking)中initWithBaseURL只是拼接了基URL和后来的路径,并且规定了默认的请求序列和响应序列。而在我们的实际工程中肯定需要更多的属性设置,例如延时要求,缓存要求等。这里的属性设置具体原因不用细究,只要知道这个方法的作用就好。

    -(instancetype)initWithBaseURL:(NSURL *)url
    {
    self = [super initWithBaseURL:url];
    if (self) {
        // 请求超时设定
        self.requestSerializer.timeoutInterval = 5;
        self.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
        [self.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Accept"];
        [self.requestSerializer setValue:url.absoluteString forHTTPHeaderField:@"Referer"];
        
        self.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/plain", @"text/javascript", @"text/json", @"text/html", nil];
        
        self.securityPolicy.allowInvalidCertificates = YES;
    }
    return self;
    }
    

    然后就是封装AFNetworking的部分了。这里我只是封装了一个请求数据的方法,比较简单,当然不能下载和上传,以后我会完善。

    首先我们要在文件头定义两个block,在请求中我们需要用block回调数据来使用,因为AFNetworking请求数据方法中success块是没有返回值的,所以我们需要自己声明块来回调请求到的json数据再进行解析。

    块定义如下

    //请求成功回调block
    typedef void (^requestSuccessBlock)(NSDictionary *dic);
    
    //请求失败回调block
    typedef void (^requestFailureBlock)(NSError *error);
    

    为了分辨方法,我也定了也一个HTTP方法类来封装方法。

    //请求方法define
    typedef enum {
    GET,
    POST,
    PUT,
    DELETE,
    HEAD
    } HTTPMethod;
    

    接下来就是方法封装了

    - (void)requestWithMethod:(HTTPMethod)method
                 WithPath:(NSString *)path
               WithParams:(NSDictionary*)params
         WithSuccessBlock:(requestSuccessBlock)success
          WithFailurBlock:(requestFailureBlock)failure
    {
    switch (method) {
        case GET:{
             [self GET:path parameters:params progress:nil success:^(NSURLSessionTask *task, NSDictionary * responseObject) {
                   NSLog(@"JSON: %@", responseObject);
                   success(responseObject);
             } failure:^(NSURLSessionTask *operation, NSError *error) {
                   NSLog(@"Error: %@", error);
                   failure(error);
                }];
                break;
           }
        case POST:{
             [self POST:path parameters:params progress:nil success:^(NSURLSessionTask *task, NSDictionary * responseObject) {
                  NSLog(@"JSON: %@", responseObject);
                   success(responseObject);
                } failure:^(NSURLSessionTask *operation, NSError *error) {
                    NSLog(@"Error: %@", error);
                    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:error.localizedDescription delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
                   [alert show];
                    failure(error);
                }];
                break;
            }
        default:
            break;
        }    
    }
    

    我只是简单的封装了GET和POST两个方法,成功会讲请求到的json数据存储到一个字典responseObject中,然后通过回调方法在页面中使用,失败的话则回调了NSError *error

    在ViewController中,我们即可用一个方法完成网络请求。

    [[cbsNetWork sharedManager] requestWithMethod:GET WithPath:@"get" WithParams:nil WithSuccessBlock:^(NSDictionary *dic) {
        Model *model = [MTLJSONAdapter modelOfClass:[Model class] fromJSONDictionary:dic error:nil];
        NSLog(@"%@", model.origin);
    } WithFailurBlock:^(NSError *error) {
        
    }];
    

    PS:我的model是用Mantle解析的,所以想复制代码的同学自己用自己的办法写model吧,这不是重点。

    这样简单的网络请求就封装好了,当然还可以更加完善可以再加一层专门写model解析数据这样你的ViewController代码会更简洁。

    这篇文章写的代码我会传到我的github上,有需要的可以点开来下载看。

    学艺不精,有错误的地方还请指出,我将万分感谢。也欢迎来我的博客看看。

    相关文章

      网友评论

      • 小强01:你好,我pod导入mantle文件。然后就报这种错误 "_OBJC_METACLASS_$_MTLModel", referenced from:
        _OBJC_METACLASS_$_Model in Model.o。是我哪里没有导入配置文件吗?
      • 正直的豆豆:你好,请问为什么通过error.userInfo[@"com.alamofire.serialization.response.error.data"] 获取错误信息获取不到啊??
      • c6b04035c7d8:post请求,如果客户端传入的数据是一个数据流,后台用WebUtil.getRequest().getInputStream();接收时,发现只能接收到请求,却接受不到数据,我想应该是AFNetworking 3.0里面限制了,是这样的么?如果不是AFNetworking 3.0限制的话,客户端怎么传入这个数据流呢?
      • 鬼谷门生: [self.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Accept"];
        [self.requestSerializer setValue:url.absoluteString forHTTPHeaderField:@"Referer"];

        请问你这个是请求头配置吗?
      • IT:AFN上层已经封装好单例了 应该就不用LZ自己再封装了
      • 6bbba3902a7a:因为Ipv6被拒了,你这个封装可以支持吗
      • 青青青青:你的单例里面block里面那个self是不是造成了循环引用?
      • 我的大好时光:这种封装存在个严重的问题,比如说:我进入一个正在加载网络数据的界面,数据很大,等了很久没反应,用户手动点击了返回,你怎么取消当前正在加载的网络请求?,因为你是单利,所以说,界面消失,请求还是会继续。
        59cd97c56858:@简述轶事 因为是继承AFN的,所以在封装的工具类里面写一个方法,[self.operationQueue cancelAllOperations]; 在页面销毁的时候调用这个方法
      • 选一个昵称也被使用了:不错,正好在封装的,看了公司以前的项目也是这么封装的,单例那里一直没看懂,可能还是网络层知识太薄弱了 :pensive:
      • CoderChou:请问增加上传和下载吗
      • gary成:请问设置了超时的时间,可是根本没有效果,该怎么解决
        coder_那一抹刚吹过的风:@gary成 你设置的姿势有问题,换个姿势来一次
      • 鼻毛长长:为何这里要用到单例模式?因为这个manager应该是系统中唯一的实例。

        ???因为应该是唯一实例,所以要用单例模式???

        为什么应该是唯一实例?如果不是唯一实例会造成什么样的后果?
        44d555da4ba6:http://www.jianshu.com/p/5969bbb4af9f
      • 追寻那一米阳光:你好,我想问下我们后台给我返回的错误信息error.userInfo[@"com.alamofire.serialization.response.error.data"]在这里边存着,这样是正常吗
        cbsfly_:@王延磊iOS开发 要问后台吧。。。
        追寻那一米阳光:@王延磊iOS开发 这个错误信息怎么处理
      • CNMD_LJ:为什么不用task.taskIdentifier来作为id而选择task.hash呢
        cbsfly_:@塔格木洛洛 哪里。。。
      • 不简单的风度:你好,我想问下,写成单例的话,如果有多个网络请求会不会卡线程
        cbsfly_:@不简单的风度 我明白你的意思了 其实我也是新手,没考虑过多线程进行网络请求的情况,所以可能要考虑加互斥锁之类的,不好意思考虑不周 有时间我也会更进一步的探究,有进展了我也会把我的看法写出来的 :blush:
        不简单的风度:@巴拉森 可是单例的话全局只有一份,如果一个请求还没完成然后又去发起另一个请求呢?不好意思我是新手,你说的我有点不太明白😊
        cbsfly_:@不简单的风度 不会的 这只是单例模式创建一个manager而已 创建完毕后就会退出 以后也不会再执行
      • 万恶胖为首:thx
        对我使用AFNetworking 有帮助。

      本文标题:用AFNetworking3.0封装网络请求

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