天天看点

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博、微信等,这些应用本身可能采用ios开发,但是所有的数据支撑都是基于后台网络服务器的。如今,网络编程越来越普遍,孤立的应用通常是没有生命力的。今天就会给大家介绍这部分内容:

<a href="http://www.cnblogs.com/kenshincui/p/4042190.html#requestandresponse">web请求和响应</a>

使用代理方法

简化请求方法

图片缓存

扩展--文件分段下载

扩展--文件上传

<a href="http://www.cnblogs.com/kenshincui/p/4042190.html#nsurlsession">nsurlsession</a>

数据请求

文件上传

文件下载

会话

<a href="http://www.cnblogs.com/kenshincui/p/4042190.html#uiwebview">uiwebview</a>

浏览器实现

uiwebview与页面交互

<a href="http://www.cnblogs.com/kenshincui/p/4042190.html#networkstatus">网络状态</a>

目 录

做过web开发的朋友应该很清楚,http是无连接的请求。每个请求request服务器都有一个对应的响应response,无论是asp.net、jsp、php都是基于这种机制开发的。

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

在web开发中主要的请求方法有如下几种:

get请求:get是获取数据的意思,数据以明文在url中传递,受限于url长度,所以传输数据量比较小。

post请求:post是向服务器提交数据的意思,提交的数据以实际内容形式存放到消息头中进行传递,无法在浏览器url中查看到,大小没有限制。

head请求:请求头信息,并不返回请求数据体,而只返回请求头信息,常用用于在文件下载中取得文件大小、类型等信息。

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

程序的实现需要借助几个对象:

nsurlrequest:建立了一个请求,可以指定缓存策略、超时时间。和nsurlrequest对应的还有一个nsmutableurlrequest,如果请求定义为nsmutableurlrequest则可以指定请求方法(get或post)等信息。

nsurlconnection:用于发送请求,可以指定请求和代理。当前调用nsurlconnection的start方法后开始发送异步请求。

程序代码如下:

运行效果:

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

需要注意:

根据响应数据大小不同可能会多次执行- (void)connection:(nsurlconnection *)connection didreceivedata:(nsdata *)data方法。

url中不能出现中文(例如上面使用get传参数时,file参数就可能是中文),需要对url进行编码,否则会出错。

当然,对于上面文件下载这种大数据响应的情况使用代理方法处理响应具有一定的优势(可以获得传输进度)。但是如果现响应数据不是文件而是一段字符串(注意web请求的数据可以是字符串或者二进制,上面文件下载示例中响应数据是二进制),那么采用代理方法处理服务器响应就未免有些太麻烦了。其实苹果官方已经提供了下面两种方法处理一般的请求:

+ (void)sendasynchronousrequest:request: queue:queue:completionhandler:发送一个异步请求

+ (nsdata *)sendsynchronousrequest: returningresponse: error:发送一个同步请求

请求过程中需要传递一个用户名和密码,如果全部正确则服务器端返回此用户可以看到的最新微博数据,响应的json格式大致如下:

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

整个json最外层是statuses节点,它是一个数组类型,数组中每个元素都是一条微博数据,每条微博数据中除了包含微博信息还包含了发表用户的信息。

首先需要先定义用户模型kcuser

微博模型kcstatus

kcstatus.h

kcstatus.m

其次需要自定义微博显示的单元格kcstatustableviewcell,这里需要注意,由于服务器返回数据中头像和会员类型图片已经不在本地,需要从服务器端根据返回json的中图片的路径去加载。

kcstatustableviewcell.h

kcstatustableviewcell.m

最后就是kcmainviewcontroller,在这里需要使用nsurlconnection的静态方法发送请求、获得请求数据,然后对请求数据进行json序列化,将json字符串序列化成微博对象通过uitableview显示到界面中。

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

可以看到使用nsurlconnection封装的静态方法可以直接获得nsdata,不需要使用代理一步步自己组装数据。这里采用了post方式发送请求,使用post发送请求需要组装数据体,不过数据长度不像get方式存在限制。从ios5开始苹果官方提供了json序列化和反序列化相关方法(上面程序中仅仅用到了反序列化方法,序列化使用datawithjsonobject:options:opt error:方法)方便的对数组和字典进行序列化和反序列化。但是注意反序列化参数设置,程序中设置成了0,直接反序列化为不可变对象以提高性能。

注意: 1.现在多数情况下互联网数据都是以json格式进行传输,但是有时候也会面对xml存储。在ios中可以使用nsxmlparser进行xml解析,由于实际使用并不多,在此不再赘述。 2.使用kvc给对象赋值时(通常是nsdictionary或nsmutalbedictionary)注意对象的属性最好不要定义为基本类型(如int),否则如果属性值为null则会报错,最后定义为objc对象类型(如使用nsnumber代替int等);

开发web类的应用图片缓存问题不得不提及,因为图片的下载相当耗时。对于前面的微博数据,头像和微博类型图标在数据库中是以链接形式存放的,取得链接后还必须进行对应的图片加载。大家都知道图片往往要比文本内容大得多,在uitableview中上下滚动就会重新加载数据,对于文本由于已经加载到本地自然不存在问题,但是对于图片来说如果每次都必须重新从服务器端加载就不太合适了。

解决图片加载的办法有很多,可以事先存储到内存中,也可以保存到临时文件。在内存中存储虽然简单但是往往不可取,因为程序重新启动之后还面临这重新请求的问题,类似于新浪微博、qq、微信等应用一般会存储在文件中,这样应用程序即使重启也会从文件中读取。但是使用文件缓存图片可能就要自己做很多事情,例如缓存文件是否过期?缓存数据越来越大如何管理存储空间?

这些问题其实很多第三方框架已经做的很好了,实际开发中往往会采用一些第三方框架来处理图片。例如这里可以选用sdwebimage框架。sdwebimage使用起来相当简单,开发者不必过多关心它的缓存和多线程加载问题,一个方法就可以解决。这里直接修改kcstatustableviewcell中相关代码即可:

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

在上面的方法中直接调用了sdwebimage的分类缓存方法设置图片,这个方法可以分配另外一个线程去加载图片(同时对于头像还指定了默认图片,网速较慢时不至于显示空白),图片加载后存放在沙箱的缓存文件夹,如下图:

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

通过前面的演示大家应该对于ios的web请求有了大致的了解,可以通过代理方法接收数据也可以直接通过静态方法接收数据,但是实际开发中更推荐使用静态方法。关于前面的文件下载示例,更多的是希望大家了解代理方法接收响应数据的过程,实际开发中也不可能使用这种方法进行文件下载。这种下载有个致命的问题:不适合进行大文件分段下载。因为代理方法在接收数据时虽然表面看起来是每次读取一部分响应数据,事实上它只有一次请求并且也只接收了一次服务器响应,只是当响应数据较大时系统会重复调用数据接收方法,每次将已读取的数据拿出一部分交给数据接收方法。这样一来对于上g的文件进行下载,如果中途暂停的话再次请求还是从头开始下载,不适合大文件断点续传(另外说明一点,上面nsurlconnection示例中使用了nsmutabledata进行数据接收和追加只是为了方便演示,实际开发建议直接写入文件)。

实际开发文件下载的时候不管是通过代理方法还是静态方法执行请求和响应,我们都会分批请求数据,而不是一次性请求数据。假设一个文件有1g,那么只要每次请求1m的数据,请求1024次也就下载完了。那么如何让服务器每次只返回1m的数据呢?

在网络开发中可以在请求的头文件中设置一个range信息,它代表请求数据的大小。通过这个字段配合服务器端可以精确的控制每次服务器响应的数据范围。例如指定bytes=0-1023,然后在服务器端解析range信息,返回该文件的0到1023之间的数据的数据即可(共1024byte)。这样,只要在每次发送请求控制这个头文件信息就可以做到分批请求。

当然,为了让整个数据保持完整,每次请求的数据都需要逐步追加直到整个文件请求完成。但是如何知道整个文件的大小?其实在前面的文件下载演示中大家可以看到,可以通过头文件信息获取整个文件大小。但是这么做的话就必须请求整个数据,这样分段下载就没有任何意义了。所幸在web开发中我们还有另一种请求方法“head”,通过这种请求服务器只会响应头信息,其他数据不会返回给客户端,这样一来整个数据的大小也就可以得到了。下面给出完整的程序代码,关键的地方已经给出注释(为了简化代码,这里没有使用代理方法):

kcmainviewcontroller.m

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

下载文件的生成过程:

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

在做web应用程序开发时,如果要上传一个文件往往会给form设置一个enctype=”multipart/form-data”的属性,不设置这个值在后台无法正常接收文件。在web开发过程中,form的这个属性其实本质就是指定请求头中content-type类型,当然使用get方法提交就不用说了,必须使用url编码。但是如果使用post方法传递数据其实也是类似的,同样需要进行编码,具体编码方式其实就是通过enctype属性进行设置的。常用的属性值有:

application/x-www-form-urlencoded:默认值,发送前对所有发送数据进行url编码,支持浏览器访问,通常文本内容提交常用这种方式。

multipart/form-data:多部分表单数据,支持浏览器访问,不进行任何编码,通常用于文件传输(此时传递的是二进制数据) 。

text/plain:普通文本数据类型,支持浏览器访问,发送前其中的空格替换为“+”,但是不对特殊字符编码。

application/json:json数据类型,浏览器访问不支持 。

text/xml:xml数据类型,浏览器访问不支持。

要实现文件上传,必须采用post上传,同时请求类型必须是multipart/form-data。在web开发中,开发人员不必过多的考虑mutiparty/form-data更多的细节,一般使用file控件即可完成文件上传。但是在ios中如果要实现文件上传,就没有那么简单了,我们必须了解这种数据类型的请求是如何工作的。

下面是在浏览器中上传一个文件时,发送的请求头:

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

这是发送的请求体内容:

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

在请求头中,最重要的就是content-type,它的值分为两部分:前半部分是内容类型,前面已经解释过了;后面是边界boundary用来分隔表单中不同部分的数据,后面一串数字是浏览器自动生成的,它的格式并不固定,可以是任意字符。和请求体中的源代码部分进行对比不难发现其实boundary的内容和请求体的数据部分前的字符串相比少了两个“--”。请求体中content-disposition中指定了表单元素的name属性和文件名称,同时指定了content-type表示文件类型。当然,在请求体中最重要的就是后面的数据部分,它其实就是二进制字符串。由此可以得出以下结论,请求体内容由如下几部分按顺序执行组成:

了解这些信息后,只要使用post方法给服务器端发送请求并且请求内容按照上面的格式设置即可。

下面是实现代码:

nsurlconnection是2003年伴随着safari一起发行的网络开发api,距今已经有十一年。当然,在这十一年间它表现的相当优秀,有大量的应用基础,这也是为什么前面花了那么长时间对它进行详细介绍的原因。但是这些年伴随着iphone、ipad的发展,对于nsurlconnection设计理念也提出了新的挑战。在2013年wwdc上苹果揭开了nsurlsession的面纱,将它作为nsurlconnection的继任者。相比较nsurlconnection,nsurlsession提供了配置会话缓存、协议、cookie和证书能力,这使得网络架构和应用程序可以独立工作、互不干扰。另外,nsurlsession另一个重要的部分是会话任务,它负责加载数据,在客户端和服务器端进行文件的上传下载。

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

通过前面的介绍大家可以看到,nsurlconnection完成的三个主要任务:获取数据(通常是json、xml等)、文件上传、文件下载。其实在nsurlsession时代,他们分别由三个任务来完成:nsurlsessiondata、nsurlsessionuploadtask、nsurlsessiondownloadtask,这三个类都是nsurlsessiontask这个抽象类的子类,相比直接使用nsurlconnection,nsurlsessiontask支持任务的暂停、取消和恢复,并且默认任务运行在其他非主线程中,具体关系图如下:

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

前面通过请求一个微博数据进行数据请求演示,现在通过nsurlsessiondatatask实现这个功能,其实现流程与使用nsurlconnection的静态方法类似,下面是主要代码:

下面看一下如何使用nsurlsessionuploadtask实现文件上传,这里贴出主要的几个方法:

如果仅仅通过上面的方法或许文件上传还看不出和nsurlconnection之间的区别,因为拼接上传数据的过程和前面是一样的。事实上在nsurlsessionuploadtask中还提供了一个- (nsurlsessionuploadtask *)uploadtaskwithrequest:(nsurlrequest *)request fromfile:(nsurl *)fileurl completionhandler:(void (^)(nsdata *data, nsurlresponse *response, nserror *error))completionhandler方法用于文件上传。这个方法通常会配合“put”请求进行使用,由于put方法包含在web dav协议中,不同的web服务器其配置启用put的方法也不同,并且出于安全考虑,各类web服务器默认对put请求也是拒绝的,所以实际使用时还需做重分考虑,在这里不具体介绍,有兴趣的朋友可以自己试验一下。

使用nsurlsessiondownloadtask下载文件的过程与前面差不多,需要注意的是文件下载文件之后会自动保存到一个临时目录,需要开发人员自己将此文件重新放到其他指定的目录中。

nsurlconnection通过全局状态来管理cookies、认证信息等公共资源,这样如果遇到两个连接需要使用不同的资源配置情况时就无法解决了,但是这个问题在nsurlsession中得到了解决。nsurlsession同时对应着多个连接,会话通过工厂方法来创建,同一个会话中使用相同的状态信息。nsurlsession支持进程三种会话:

<code>defaultsessionconfiguration</code>:进程内会话(默认会话),用硬盘来缓存数据。

<code>ephemeralsessionconfiguration</code>:临时的进程内会话(内存),不会将cookie、缓存储存到本地,只会放到内存中,当应用程序退出后数据也会消失。

<code>backgroundsessionconfiguration</code>:后台会话,相比默认会话,该会话会在后台开启一个线程进行网络数据处理。

下面将通过一个文件下载功能对两种会话进行演示,在这个过程中也会用到任务的代理方法对上传操作进行更加细致的控制。下面先看一下使用默认会话下载文件,代码中演示了如何通过nsurlsessionconfiguration进行会话配置,如果通过代理方法进行文件下载进度展示(类似于前面中使用nsurlconnection代理方法,其实下载并未分段,如果需要分段需要配合后台进行),同时在这个过程中可以准确控制任务的取消、挂起和恢复。

演示效果:

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

nsurlsession支持程序的后台下载和上传,苹果官方将其称为进程之外的上传和下载,这些任务都是交给后台守护线程完成的,而非应用程序本身。即使文件在下载和上传过程中崩溃了也可以继续运行(注意如果用户强制退关闭应用程序,nsurlsession会断开连接)。下面看一下如何在后台进行文件下载,这在实际开发中往往很有效,例如在手机上缓存一个视频在没有网络的时候观看(为了简化程序这里不再演示任务的取消、挂起等操作)。下面对前面的程序稍作调整使程序能在后台完成下载操作:

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

当nsurlsession在后台开启几个任务之后,如果有其中几个任务完成后系统会调用此应用程序的-(void)application:(uiapplication *)application handleeventsforbackgroundurlsession:(nsstring *)identifier completionhandler:(void (^)())completionhandler代理方法;此方法会包含一个competionhandler(此操作表示应用完成所有处理工作),通常我们会保存此对象;直到最后一个任务完成,此时会重新通过会话标识(上面sessionconfig中设置的)找到对应的会话并调用nsurlsession的-(void)urlsessiondidfinisheventsforbackgroundurlsession:(nsurlsession *)session代理方法,在这个方法中通常可以进行ui更新,并调用completionhandler通知系统已经完成所有操作。具体两个方法代码示例如下:

网络开发中还有一个常用的ui控件uiwebview,它是ios中内置的浏览器控件,功能十分强大。如一些社交软件往往在应用程序内不需要打开其他浏览器就能看一些新闻之类的页面,就是通过这个控件实现的。需要注意的是uiwebview不仅能加载网络资源还可以加载本地资源,目前支持的常用的文档格式如:html、pdf、docx、txt等。

下面将通过一个uiwebview开发一个简单的浏览器,界面布局大致如下:

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

在这个浏览器中将实现这样几个功能:

1.如果输入以”file://”开头的地址将加载bundle中的文件

2.如果输入以“http”开头的地址将加载网络资源

3.如果输入内容不符合上面两种情况将使用bing搜索此内容

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

其实uiwebview整个使用相当简单:创建url-&gt;创建请求-&gt;加载请求,无论是加载本地文件还是web内容都是这三个步骤。uiwebview内容加载事件同样是通过代理通知外界,常用的代理方法如开始加载、加载完成、加载出错等,这些方法通常可以帮助开发者更好的控制请求加载过程。

注意:uiwebview打开本地pdf、word文件依靠的并不是uiwebview自身解析,而是依靠mime type识别文件类型并调用对应应用打开。

uiwebview与页面的交互主要体现在两方面:使用objc方法进行页面操作、在页面中调用objc方法两部分。和其他移动操作系统不同,ios中所有的交互都集中于一个stringbyevaluatingjavascriptfromstring:方法中,以此来简化开发过程。

1.首先在request方法中使用loadhtmlstring:加载了html内容,当然你也可以将html放到bundle或沙盒中读取并且加载。

2.然后在webviewdidfinishload:代理方法中通过stringbyevaluatingjavascriptfromstring: 方法可以操作页面中的元素,例如在下面的方法中读取了页面标题、修改了其中的内容。

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

页面中的js是无法直接调用objc方法的,但是可以变换一下思路:当需要进行一个js操作时让页面进行一个重定向,并且在重定向过程中传入一系列参数。在uiwebview的代理方法中有一个webview: shouldstartloadwithrequest:navigationtype方法,这个方法会在页面加载前执行,这样可以在这里拦截重定向,并且获取定向url中的参数,根据这些参数约定一个方法去执行。

当访问百度搜索手机版时会发现,有时候点击页面中的某个元素可以调出ios操作系统的uiactionsheet,下面不妨模拟一下这个过程。首先需要定义一个js方法,为了方便扩展,这个js保存在myjs.js文件中存放到bundle中,同时在页面中加载这个文件内容。myjs.js内容如下:

这个js的功能相当单一,调用showsheet方法则会进行一个重定向,调用过程中需要传递一系列参数,当然这些参数都是uiactionsheet中需要使用的,注意这里约定所有调用uiactionsheet的方法参数都以”kcactionsheet”开头。

然后在webview: shouldstartloadwithrequest:navigationtype方法中截获以“kcactionsheet”协议开头的请求,对于这类请求获得对应参数调用uiactionsheet。看一下完整代码:

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

前面无论是下载还是上传都没有考虑网络状态,事实上实际开发过程中这个问题是不得不思考的,试想目前谁会用3g或4g网络下载一个超大的文件啊,因此实际开发过程中如果程序部署到了真机上必须根据不同的网络状态决定用户的操作,例如下图就是在使用qq音乐播放在线音乐的提示:

iOS开发系列--网络开发概览Web请求和响应NSURLSessionUIWebView网络状态

<a href="http://pan.baidu.com/s/1hqkwt3u" target="_blank"></a>