在开始iOS网络编程之前,首先要了解iOS的网络编程架构,如下图:
每个iOS应用程序都建立在网络编程框架的四层结构之上,最顶端的是Cocoa层,包括了Objective-C API,如URL加载,Bonjour和Game Kit;Cocoa之下的是Core Foundation,包含了CFNetwork API,这也是大部分应用级别网络编程代码的基础,CFNetwork提供了最简单的网络编程接口,这些接口是建立在CFStream和CFSocket基础上的,这两个类是封装在BSD socket之上的轻量级应用,而BSD socket则是最接近天线底层硬件的应用。BSD socket是完全基于C的,提供了与远程设备或服务器交互的完全控制功能。随着使用的API更加贴近于底层,编程人员所获得的网络控制能力越强,但是同时也会失去良好的抽象和易用性。虽然有时候会需要直接使用底层的API,但是Apple推荐使用CFNetwork及其以上层次的API。单纯的BSD级别的socket编程不能提供系统级别的VPN也不能激活Wi-Fi或者蜂窝无线电,但是这些功能CFNetwork都可以向你提供。
在开始应用级别的网络编程之前,一定要先清楚各个层次上的API为你提供了哪些功能以及如何使用他们,下面我们来看一下这些API
iOS网络编程API
每一层的API都提供了一定了网络编程功能供程序员使用,每一层都比他下一层的API提供了更好的抽象,但是抽象的代价就是控制力,下面我们来看一下iOS都为程序员提供了哪些网络编程的API。
.NSURLConnection
NSURLConnection是Cocoa层的API,提供了最简单的URL请求,与网络服务相互交互,获取视频图像和HTML格式的文件等功能。他是建立在NSStream基础上的,同时也提供了对四种最主要的URI规范的支持,file,HTTP,HTTPS和FTP。虽然NSURLConnection限制了交互所使用的协议,但是却抽象除了很多更低层次的读写过程以及缓冲机制等,还包含了对认证的支持以及缓存引擎等。NSURLConnection接口比较稀疏,主要是基于NSURLConnectionDelegate协议的,能够使得应用程序在生命周期内随时进行网络连接工作。NSURLConnection请求默认情况下是异步的,但是也可以设置为发送同步请求。同步请求会阻断请求线程,应该根据应用程序的需要来进行选择请求的类型。
Game Kit
Game Kit作为网络编程的核心,提供了点对点的网络连接方式,在传统的网络配置中,Game Kit是建立在Bonjour之上的,但是Game Kit不需要网络设施来运作,他可以创建ad-hocBluetooth Personal Area Networks PAN,在较小区域内或者没有网络基础设施的情况下互相共享资源。Game Kit在创建网络的时候仅仅需要一个会话标识和连接模型,不需要进行socket配置或者底层的网络交互来进行点对点的连接。Game Kit通过GKSessionDelegate协议进行交互。
Bonjour
Bonjour是Apple的零配置网络的实现,Bonjour提供了发现和连接网络设备和服务的机制,可以忽略设备的网络地址,相应的,Bonjour使用的是名称、服务类型、和域来识别服务。Bonjour抽象了底层的网络对于底层的组播DNS和基于DNS的Service Discovery。在Cocoa层,NSNetService API提供了发布和识别Bonjour地址信息的接口,你可以使用NSNetServiceBrowser API来发现可网络上可用的服务。即使是使用Cocoa层的API发布Bonjour服务,也需要对于Core Foundation有所了解才能配置socket的通信。
NSStream
NSStream处于Cocoa层的API,是建立在CFNetwork基础之上的,作为NSURLConnection的基础服务,以及处理底层的网络任务。与NSURLConnection似,NSStream提供了与远程服务器或本地文件的交互机制。但是你可以使用NSStream来通过诸如telnet或者SMTP协议进行交互,而这些协议是NSURLConnection不支持的。但是NSStream提供了这些额外的服务也是有代价的,NSStream没有内置的HTTP或HTTPS状态支持和认证支持。他将数据接受到C缓冲期中,他也不能管理多重外界请求,这些需要通过继承子类才能够提供支持。NSStream是异步的,通过NSStreamDelegte进行交互。
CFNetwork
CFNetwork API是基于BSD socket之上的,用来实现NSStream和URL加载系统以及Bonjour和Game Kit API。他提供了对一些协议如HTTP和FTP的更加高级的支持。CFNetwork与BSD socket的主要区别在于运行循环集成。如果系统使用CFNetwork,输入和输出时间会排列在线程的运行循环当中。如果输入和输出时间发生在另外的现成中,那么需要程序员亲自开启适合的运行循环模式。CFNetwork比URL加载系统提供了更多的配置选项,这是一把双刃剑。这些配置选项在通过CFNetwork创建HTTP请求的时候是可见的。当你创建请求的时候,必须手动添加HTTP头和cookies,而NSURLConnection可以自动添加标准的头和cookie。CFNetwork基础设施是建立在CFSocket和CFStream API之上的,这两个都是Core Foundation层的核心。CFNetwork包括了针对CFFTP等协议来与FTP服务进行交互,还有CFHTTP来发送和接收HTTP消息,以及CFNetService来发布和浏览Bonjour服务。
BSD Sockets
BSD socket建立了网络活动的底层基础,也是整个网络编程结构的最底层。BSD sockets是通过C实现,但是可以在Objective-C代码中使用。一般不推荐使用BSD socket API,因为他没有与操作系统相挂钩。例如,BSD sockets没有涉及到系统级别的VPN以及调用自动激活的Wi-Fi的API或者是蜂窝无线电。Apple推荐仅使用CFNetwork或者更高层次的API。
当使用不同层次的API的时候,还需要理解他们是怎样集成到你的应用当中的,下面来看一下运行循环的概念,他用来通过操作系统监视网络事件,然后将这些时间转发给应用。
运行循环
运行循环以NSRunLoop为代表,是线程的基础类使得操作系统可以唤醒休眠的线程来响应进入的事件。一个运行循环配置了可以在安排一定时间内的任务顺序和接收到的事件。iOS应用中的每个线程都可以有最多一个运行循环。对于主线程来讲运行循环是自动开始的,在应用代理的applicationDidFinishLaunchingWithOptions方法调用后可以获取到这个运行循环。
如果需要的话第二个线程中必须显示地运行他的运行循环。在第二个线程中运行运行循环之前,必须添加至少一个输入源或者定时器,否则的话运行循环将会马上退出。运行循环为程序员提供了与线程交互的方式,但是这并不是必须的。线程的可以在不启用运行循环的情况下进行许多数据的处理,但是如果需要与网络进行交互的话就需要启动运行循环。 运行循环接受时间可以有两种类型,输入源和定时器。输入源可以是基于端口的也可以是自定义的,它将事件异步传递给应用程序。这两种方式最主要的区别在于基于端口的方式是自动的而自定义源需要手动从不同的线程启动信号。可以通过实现一些CFRunLoopSourceRef的回调函数来创建自定义输入源。定时器是基于时间的事件通知机制,在未来的某个特定之间来执行某个任务。定时器事件是同步的而且与特定的模式相关,后面会详细介绍。如果某个模式的没有被监听,那么事件将会被忽略,线程将不会被通知对应的事件,知道该线程以相应的模式运行。
定时器可以配置为执行一次或者重复执行。任务执行的顺序安排是基于安排的时间而不是实际的执行时间。如果在运行循环执行应用程序的句柄方法的时候激发,那么这个事件将会被等到下一次运行循环调用时间句柄的时候激发,通常是通过@selector方法。如果这个事件巡回到下次同样的时间被激发的时候,那么将会被激发最近的事件而之前的事件将会被覆盖。
运行循环也有监听器,他不会被监听,它使得对象在运行循环执行某个活动的时候可以接收到回调函数,这些事件也包括运行循环的开始和结束以及沉睡和唤醒,还包括在开始处理输入源或者定时器的事件。在CFRunLoopActivity的相关文档中有详细的说明。监听器可以被配置为激发一次,然后将其移除,也可以配置为重复运行。可以通过Core Founation函数CFRunLoopObserverRef()来添加运行循环监听器。
运行循环模式
运行循环都是通过某个配置的模式运行的。运行循环模型用来被操作系统过滤源和允许传递的时间,例如代理方法的调用。主要有两种运行循环的模式,NSDefaultRunLoopMode(Core Foundation的kCFRunLoopDefaultMode)和NSRunLoopCommonModes(Core Foundation的kCFRunLoopCommonModes)。前者是系统默认的,通常在开始运行循环和配置输入源的时候使用;后者是一系列可配置模式的集合。可以通过scheduleInRunLoop:forMode:方法来将NSRunLoopCommonMode设定给一个输入源,然后将该对象与组内现有的所有模式关联起来。
虽然NSRunLoopCommonModes是可配置的,但是它也是处于底层的需要调用Core Foundation的CFRunLoopAddCommonMode函数。这样可以自动的注册输入源,定时器和监听器为新的模式,而不用手动将他们添加到新的模式中。也可以通过制定自定义字符串如@"CustomRunLoopMode"来配置自定义运行循环模式。为了使得自定义的运行循环生效,必须至少添加一个输入源或者监听器。