来源:zongmumask

链接:http://www.jianshu.com/p/8eac5b1975de

简述

在iOS开发中,与直接使用苹果框架中提供的NSURLConnection或NSURLSession进行网络请求相比,使用AFNetworking会有哪些好处?当同时发起多个网络请求AFNetworking是如何实现并发的,在并发的时候,AFNetworking是如何管理线程的?苹果重构NSURLConnetion推出新的网络加载系统NSURLSession解决了什么问题或者是与NSURLConnection相比NSURLSession有好些好处?上面问题的答案会贯穿在这篇文章中(本篇文章只涉及了与操作队列,多线程相关的分析)。

不用网络框架进行网络请求

NSURLConnection的简单使用(下面的代码均只为了演示,更详细的使用方法请自行谷歌)

NSURLConnection提供了两个类方法用于发起同步或异步请求,对于异步请求来说必然是在子线程中发起,若在主线程中发起异步网络请求会造成主线程阻塞,界面无响应,这就涉及到多线程编程。但多线程编程是一门非常细致的活,要考虑很多的问题,比如线程的生命周期,多线程资源竞争,加锁,避免死锁,稍不留意就会踩到坑里。好在苹果的接口使用极其方便,你甚至不需要理解多线程就能轻松使用上多线程了,发起异步请求的接口内部已经实现了多线程相关的操作。越是高级的接口,其隐藏的细节就越多,对于开发者来说当然是很方便,但若还能对其实现有所理解那就更完美了。当子线程发起了异步请求后会阻塞以等待网络响应,那应该由谁来处理网络响应呢?苹果提供了两种方式,一种是block,提供一个处理响应的block回调。一种是代理,使用代理的话就必须实现NSURLConnectionDelegate这个协议。你只需要在有网络请求的UIViewController中调用NSURLConnection提供的类方法就可以了。但如果你的项目中有不止一个UIViewController或者有的UIViewController中都不止一个请求的话,你就需要在每一个有网络请求的UIViewController中这样写。这样写会有什么问题呢?首先会造成软件结构不清晰,没有剥离出网络层,其次没有实现网络请求统一管理,无法实现取消所有网络请求等功能,再有会出现很多重复的代码,没有让公用功能形成模块,进行复用。当然为了解决这些问题你也可以自己实现自己的网络框架。

使用NSURLConnection版本的AFNetworking

使用网络框架的好处在于可以将分散在各个视图控制器中的网络请求统一起来模块化形成网络层,降低与数据层和表现层的耦合。AFNetworking就做了这样的工作。网络上已经有很多分析基于NSURLConnection实现的AFNetworking 2.x的源代码,这里只简单说一下其实现整个流程,想要深入了解的请谷歌或查看其源代码。首先AFNetworking要解决实现接口统一和所有网络请求统一管理的问题。NSURLConnection发起有两种方式发起请求,分别是设置响应block和代理,那采用哪种方式能够实现网络请求统一管理呢?block肯定不行,因为传入响应回调block的[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]方法会立即执行。那么只能使用设置响应代理这种方式了。具体实现是将网络请求继承于NSOperation,生产网络请求,然后再将这个网络请求加入操作队列里,剩下的所有与线程相关的操作都由这个操作队列去实现。越高级易容的接口就隐藏越多的实现细节,NSOperation隐藏了线程相关的所有细节,使得开发者只需关心构建什么样的操作。AFNetworking甚至隐藏了操作,操作队列的概念,使得开发者只需关心如何设置网络相关的请求参数,响应回调等,而不用再关心当多个网络请求如何进行统一管理,这些都已经由AFNetworking内部的操作队列属性完成了。那需要为每个请求创建一个线程来发起请求吗?基于NSURLConnection的AFNetworking是只创建了一条线程来发起所有请求并阻塞以等待响应。

重构推出的NSURLSession解决了NSURLConnection哪些问题

从2013年苹果发布重构后的加载系统NSURLSession,AFNetworking这个最受欢迎的网络框架也随之发布了基于NSURLSession的实现版本。这篇文章从 NSURLConnection 到 NSURLSession对NSURLSession的使用做了介绍。既然是对NSURLConnection进行的重构,那一定是解决了NSURLConnection存在的一些问题。苹果的接口以及其简单的形式呈现给开发者,尽量把复杂,容易出错的地方以简单的接口暴露出来,这使得用户能够参考文档很快上手,但这也有一些弊端,就是开发者对其中的原理不是很了解。越高级的接口越容易使用,但其隐蔽的实现细节就越多。相信在进行多线程编程的时候,很多开发者遇到过各种各样的坑,但在iOS平台上,苹果推出了GCD,NSperation等一系列接口可以让开发者完全可以只关注业务的实现,这些接口内部已经替开发者管理好了线程的创建,销毁,多线程资源竞争等需要开发者费很多精力的事情,甚至开发者不用对多线程理解透彻都能把多线程用得得心应手。NSURLConnection已经隐藏了线程相关的操作,已经给开发者减轻了很多负担。但NSURLConnectoin只隐藏了单个网络请求的线程的相关操作,并没有提供接口来解决多个网络请求时多个线程的管理问题,譬如当有多个网络请求时是否应该使用线程池来避免不停创建与销毁线程(这个可以有NSOperationQueue很好的解决)。并且NSURLConnection不是基于HTTP/2协议的,若使用NSURLConnection发起请求则每次请求都需要经过三次握手过程,可见NSURLConnection确实有很多可以优化的地方(我只发现这些)。NSURLConnection存在的无法将多个请求关联起来的问题已经很好的由AFNetworking解决了,所以推出的NSURLSession可以说是借鉴了AFNetworking的思想,并且可以从AFNetworking的源代码中很容易的看出来。基于NSURLConnection的AFNetworking需要让网络请求继承与NSOperation,然后再将该生成的网络请求加入操作队列中,但基于NSURLSessioin的AFNetworking只需要创建一个网络请求任务就可以了,原因在于,NSURLSession内部已经维护了两个操作队列,一个是处理session的相关回调,一个是处理响应相关的回调,所以说NSURLSession是借鉴了AFNetworking的继承于NSOperation和用单线程发起并等待响应的思想(这个会在后面给出证明)。

基于NSURLSession的AFNetworking的源码分析

下图是基于NSURLSession的AFNetworking的UML图(只为展示类之间的关联关系,并没有给出每个类的所有属性和方法):

从该类图已经能够明白AFNetworking整个的工作流程。

下面进行代码分析,使用AFHTTPSessionManager进行网络请求的示例代码(具体的使用请参考AFNetworking的README文档):

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];

[manager GET:URL parameters:nil progress:nil success:^(NSURLSessionDataTask *_Nonnulltask, id _NullableresponseObject) {

dispatch_async(dispatch_get_main_queue(), ^{

//将子线程从网络拉取的数据用于主线程刷新视图

});

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

}];

通过查看manager方法代码可以看到其实现最终是调用了父类AFURLSessionManager的initWithSessionConfiguration:方法,该方法代码片段如下:

self.sessionConfiguration = configuration;

self.operationQueue = [[NSOperationQueue alloc] init];

self.operationQueue.maxConcurrentOperationCount = 1;

self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

self.responseSerializer = [AFJSONResponseSerializer serializer];

可以看到AFNetworking对数据的解析方式默认是json解析。

这里设置的代理操作队列最大的并发操作数为1是让所有请求的发起和等待网络响应均在同一条线程中执行,而不用为每一个请求都新建一条线程,这样节约了很多资源。

在响应到达后会执行AFURLSessionManager的NSURLSessionDataDelegate协议的方法,[AFURLSessioinManager URLSession:dataTask:didReceiveData:]用于查找对应的响应代理,并将后续的数据处理如数据拼接转交给该代理。该方法的实现代码如下:

AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];

[delegate URLSession:session dataTask:dataTask didReceiveData:data];

if (self.dataTaskDidReceiveData) {

self.dataTaskDidReceiveData(session, dataTask, data);

}

在数据比较大时,改方法可能会多次执行。

当数据传输完成后会调用[AFURLSessioinManager URLSession:task:didCompleteWithError],该方法用于让对应的代理执行NSURLSessionTaskDelegate协议中的方法,并将该代理对象从字典中移除,源代码如下:

AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

// delegate may be nil when completing a task in the background

if (delegate) {

[delegate URLSession:session task:task didCompleteWithError:error];

[self removeDelegateForTask:task];

}

if (self.taskDidComplete) {

self.taskDidComplete(session, task, error);

}

随后代理执行URLSession:task:didCompleteWithError:,该方法把数据放到另一个由静态方法生成的url_session_manager_processing_queue操作队列中做数据解析,如json解析,并将解析后的数据回传到主线程或者你自己生成的操作队列里,通过通知中心将请求完成的消息传递到主线程去(后面会写一篇文章介绍通知中心的实现原理,并写一个类似的通知中心)。该方法源码片段如下:

dispatch_async(url_session_manager_processing_queue(), ^{

NSError *serializationError = nil;

responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:[NSData dataWithData:self.mutableData] error:&serializationError];

dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{

if (self.completionHandler) {

self.completionHandler(task.response, responseObject, serializationError);

}

dispatch_async(dispatch_get_main_queue(), ^{

[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];

});

});

});

整个流程可由下图表示:

所以NSURLSession的实现完全是借鉴了之前的AFNetworking,NSURLSession中维护了两个操作队列,一个用于处理发起请求,等待响应,一个用于处理响应到达后需要执行的回调,对数据进行操作。如果是使用AFHTTPSessionManager的manager方法,初始化session的操作队列的最大并发数为1,则与基于NSURLConnection的AFNetworking完全一样,所有请求和等待响应都在一条线程中执行,然后数据的解析在一个异步并发的操作队列中执行,当然你可以设置该操作队列的最大并发数。

AFNetworking源码分析的更多相关文章

  1. 【原】AFNetworking源码阅读(六)

    [原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...

  2. 【原】AFNetworking源码阅读(五)

    [原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...

  3. 【原】AFNetworking源码阅读(四)

    [原]AFNetworking源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇还遗留了很多问题,包括AFURLSessionManagerTaskDe ...

  4. AFNetWorking3.0源码分析

    分析: AFNetWorking(3.0)源码分析(一)——基本框架 AFNetworking源码解析 AFNetworking2.0源码解析<一> end

  5. AFNetworking源码阅读

    get方法: - (NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(id)parameters progress:(void ...

  6. AFNetworking源码简析

    AFNetworking基本是苹果开发中网络请求库的标配,它是一个轻量级的网络库,专门针对iOS和OS X的网络应用设计,具有模块化的架构和丰富的APIs接口,功能强大并且使用简单,深受苹果应用开发人 ...

  7. AFNetworking源码品读

    AFNetworking源码品读 AFNetworking这个库几乎是所有苹果开发人员在使用HTTP协议的第一选择,为什么这个库会有这么大的吸引力呢?其实答案就需要问问自己,为什么会用它,而不是别的库 ...

  8. iOS常用框架源码分析

    SDWebImage NSCache 类似可变字典,线程安全,使用可变字典自定义实现缓存时需要考虑加锁和释放锁 在内存不足时NSCache会自动释放存储的对象,不需要手动干预 NSCache的key不 ...

  9. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

随机推荐

  1. Java调用存储过程时报 The user specified as a definer ('root'@'%') does not exist 解决方法

    Caused by: java.sql.SQLException: The user specified as a definer (''@'') does not exist        at c ...

  2. matlab numpy equivalents

    THIS IS AN EVOLVING WIKI DOCUMENT. If you find an error, or can fill in an empty box, please fix it! ...

  3. ActionBar官方教程(11)自定义ActionBar的样式(含重要的样式属性表及练习示例)

    Styling the Action Bar If you want to implement a visual design that represents your app's brand, th ...

  4. Android 内核初识(5)Zygote进程

    简介 Zygote本身是一个Native的应用程序,和驱动.内核等均无关系.Zygote是由init进程根据init.rc文件中的配置项而创建的. zygote最初的名字叫“app_process”, ...

  5. mysql show variables系统变量详解

    mysql系统变量详解 mysqld服务器维护两种变量.全局变量影响服务器的全局操作.会话变量影响具体客户端连接相关操作. 服务器启动时,将所有全局变量初始化为默认值.可以在选项文件或命令行中指定的选 ...

  6. wps 2012-2013 通杀漏洞(CVE-2013-3934)

    测试方法: 本站提供程序(方法)可能带有攻击性,仅供安全研究与教学之用,风险自负! #!/usr/bin/python # Exploit Title: Kingsoft Office Writer ...

  7. apache开源项目--Apache POI

    Apache POI是一个开源的Java读写Excel.WORD等微软OLE2组件文档的项目.目前POI已经有了Ruby版本. 结构: HSSF - 提供读写Microsoft Excel XLS格式 ...

  8. JavaScript window.location对象

    JavaScript window.location对象   示例 注意 方法 经常使用window.location,它的结构总是记不住,简单梳理下,方便以后查询. 示例 URL:http://b. ...

  9. 安卓使用WIFI调试程序

    不知怎么回事,USB老是掉,真心烦.于是想着用WIFI可不可以调试呢,去百度一搜,果然可以.下面我说一下我的方案. 1.使用APWIFI创建WIFI热点,用安卓手机连接该热点,试试手机可以上网不. 2 ...

  10. Tomcat启动时为什么要配置CATALINA_HOME环境变量??

    CATALINA_HOME的值被设为Tomcat的安装目录,如果环境变量CATALINA_HOME已经存在,则通过这个环境变量调用bin目录下的“catalina.bat start”命令 1.Tom ...