今天博客的主题不是Alamofire, 而是iOS网络编程中经常使用的NSURLSession。如果你想看权威的NSURLSession的东西,那么就得去苹果官方的开发中心去看了,虽然是英文的,但是结合代码理解应该不难。更详细的信息请移步于苹果官方介绍URL Loading Sy ...
今天博客的主题不是Alamofire, 而是iOS网络编程中经常使用的NSURLSession。如果你想看权威的NSURLSession的东西,那么就得去苹果官方的开发中心去看了,虽然是英文的,但是结合代码理解应该不难。更详细的信息请移步于苹果官方介绍URL Loading System,网上好多iOS网络编程的博客都翻译于此。因为目前iOS开发中,网络请求大部分使用NSURLSession,所以今天的博客我们就以NSURLSession展开。关于之前使用的NSURLConnection在此就不做过多赘述了。今天博客的主要内容是系统的介绍NSURLSession及其相关的Delegate,当然每个知识点都依托于实例,如果你仔细的阅读本篇博客还是收获不少的。
下方这个截图中所涵盖的所有功能就是本篇博客中所涉及的所有知识点,几乎涵盖了NSURLSession的所有的东西。接下来我们就一个一个的功能点来详述一下NSURLSession。
一、NSURLSession概览
NSURLSession对于iOS开发来说并不是什么新的内容,它是Apple在iOS7中引入的,其主要功能是发起网络请求获取网络数据,这与iOS7之前使用的NSURLConnection功能类似,但是NSURLSession更为强大。如果在你开发的App中没有使用第三方网络库,那么NSURLSession无异于是最佳的选择。虽然网上的关于NSURLSession的东西一抓一大把,但是每个人都有每个人的见解,今天的博客就系统的整理一下NSURLSession相关的知识点,算是为下篇博客做准备吧。因为下篇博客是对Alamofire框架进行的解析,Alamofire就是对NSURLSession的封装,还是那句话,如果你对NSURLSession不熟悉的话,那么Alamofire源码看起来会比较费劲的。在本篇博客的第一部分我们先整体的概览一下NSURLSession,以便后面一步步的展开。
废话少说,进入本篇博客的主题。从NSURLSession这个名字中我们不难看出,主要是URL + Session。顾名思义,NSURLSession是用来URL会话的。当然如果你做过服务器端的开发,比如PHP,也会有Session的概念,不过此Session非彼Session,两者的区别还是不小的。iOS的NSURLSession的主要功能是通过URL与服务器简历会话的。“会话”进一步说就是交流呗,一句话总结:也就是我们的iOS客户端可以使用NSURLSession这个东西通过相应的URL与我们的服务器建立会话,然后通过此会话来完成一些交互任务(NSURLSessionTask)。Session有着不同的类型,每种类型的Session又可以执行不同类型的任务(Task)。接下来就来介绍一下Session的类型以及所执行的任务等。
1.NSURLSession的类型
在使用NSURLSession时你得知道你使用的是那种类型的Session对吧。从官方的NSURLSession API中不难看出,公有三种类型的Session:Default sessions,Ephemeral sessions,Background sessions。这三种Session我们可以通过NSURLSessionConfiguration来指定。
- 默认会话(Default Sessions)使用了持久的磁盘缓存,并且将证书存入用户的钥匙串中。
- 临时会话(Ephemeral Session)没有像磁盘中存入任何数据,与该会话相关的证书、缓存等都会存在RAM中。因此当你的App临时会话无效时,证书以及缓存等数据就会被清除掉。
- 后台会话(Background sessions)除了使用一个单独的线程来处理会话之外,与默认会话类似。不过要使用后台会话要有一些限制条件,比如会话必须提供事件交付的代理方法、只有HTTP和HTTPS协议支持后台会话、总是伴随着重定向。仅仅在上传文件时才支持后台会话,当你上传二进制对象或者数据流时是不支持后台会话的。当App进入后台时,后台传输就会被初始化。(需要注意的是iOS8和OS X 10.10之前的版本中后台会话是不支持数据任务(data task)的)。
下方的截图就是我们使用Swift语言创建了上述三种类型的会话配置,Session在初始化时可以指定下方的任意一种SessionConfiguration。具体入校所示:
2. NSURLSession的各种任务
在一个Session会话中可以发起的任务可分为三种:数据任务(Data Task)、下载任务(Download Task)、上传任务(Upload Task)。上面也提到了,在iOS8和OS X 10.10之前的版本中后台会话是不支持Data Task。下面来简述一下这三种任务。
- Data Task(数据任务)负责使用NSData对象来发送和接收数据。Data Task是为了那些简短的并且经常从服务器请求的数据而准备的。该任务可以没请求一次就对返回的数据进行一次处理。
- Download task(下载任务)以表单的形式接收一个文件的数据,该任务支持后台下载。
- Upload task(上传任务)以表单的形式上传一个文件的数据,该任务同样支持后台下载。
上面所介绍的所有类型的Session以及Session中的Task会在下方的实例中进行一一的介绍,本部分就做一个概述。
二、URL编码
1.URL编码概述
无论是GET、POST还是其他的请求,与服务器交互的URL是需要进行编码的。因为进行URL编码的参数服务器那边才能进行解析,为了能和服务器正常的交互,我们需要对我们的参数进行转义和编码。先简单的聊一下什么是URL吧,其实URL是URI(Uniform Resource Identifier ---- 统一资源定位符)的一种。URL就是互联网上资源的地址,用户可以通过URL来找到其想访问的资源。RFC3986文档规定,Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符,如果你的URL中含有汉字,那么就需要对其进行转码了。RFC3986中指定了以下字符为保留字符:! * ' ( ) ; : @ & = + $ , / ? # [ ]。
在URL编码时有一定的规则,下方是我们今天主要使用的URL格式的一个规则的一个图解。其他的我们先不说,今天博客中所涉及的主要是下图中Query的部分。从下面我们不难看出,Path和Query之间使用的是?号进行分隔的,问好后边就是我们要传给服务武器的参数了,该参数就是下方的Query的部分。在Get请求中Query是存放在URL后边,而在POST中是放在Request的Body中。如果你的参数只是一个key-Value, 那么Query的形式就是key = value。如果你的参数是一个数组比如key = [itme1, item2, item3,……],那么你的Query的格式就是key[]=item1&key[itme2]&key[item3]……。如果你的参数是一个字典比如key = ["subKey1":"item1", "subKey2":"item2"], 那么Query对应的形式就是key[subKey1]=item1&key[subKey2]=item2.接下来我们要做的就是将字典进行URL编码。
2.将Dictionary进行URL编码
在iOS开发中,有时候我们从VC层或者VM层获取到的数据是一个字典,字典中存储的就是要发给服务器的数据参数。直接将字典转成二进制数据发送给服务器,服务器那边是没法解析iOS这边的字典的,得有一个统一的交互标准,这个标准就是URL编码。我们要做的就是讲字典进行URL编码,然后将编码后的东西在传给服务器,这样一来服务器那边就能解析到我们请求的参数了。下方折叠的这段代码就是从AlamoFire框架中摘抄出来的三个方法,位于ParameterEncoding.swift文件中。该段代码就是负责将字典类型的参数进行URL编码的,在编码过程中进行转义是少不了的。
1 // - MARK - Alamofire中的三个方法该方法将字典转换成URL编码的字符 2 func query(parameters: [String: AnyObject]) -> String { 3 4 var components: [(String, String)] = [] //存有元组的数组,元组由ULR中的(key, value)组成 5 6 for key in parameters.keys.sort(<) { //遍历参数字典 7 let value = parameters[key]! 8 components += queryComponents(key, value) 9 }10 11 return (components.map { "\($0)=\($1)" } as [String]).joinWithSeparator("&")12 }13 14 15 func queryComponents(key: String, _ value: AnyObject) -> [(String, String)] {16 var components: [(String, String)] = []17 18 19 if let dictionary = value as? [String: AnyObject] { //value为字典的情况, 递归调用20 for (nestedKey, value) in dictionary {21 components += queryComponents("\(key)[\(nestedKey)]", value)22 }23 } else if let array = value as? [AnyObject] { //value为数组的情况, 递归调用24 for value in array {25 components += queryComponents("\(key)[]", value)26 }27 } else { //vlalue为字符串的情况,进行转义,上面两种情况最终会递归到此情况而结束28 components.append((escape(key), escape("\(value)")))29 }30 31 return components32 }33 34 /**35 36 - parameter string: 要转义的字符串37 38 - returns: 转义后的字符串39 */40 func escape(string: String) -> String {41 /*42 :用于分隔协议和主机,/用于分隔主机和路径,?用于分隔路径和查询参数, #用于分隔查询与碎片43 */44 let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.445 46 //组件中的分隔符:如=用于表示查询参数中的键值对,&符号用于分隔查询多个键值对47 let subDelimitersToEncode = "!$&'()*+,;="48 49 let allowedCharacterSet = NSCharacterSet.URLQueryAllowedCharacterSet().mutableCopy() as! NSMutableCharacterSet50 allowedCharacterSet.removeCharactersInString(generalDelimitersToEncode + subDelimitersToEncode)51 52 53 var escaped = ""54 55 //==========================================================================================================56 //57 // Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few58 // hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no59 // longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more60 // info, please refer to:61 //62 // - https://github.com/Alamofire/Alamofire/issues/20663 //64 //==========================================================================================================65 66 if #available(iOS 8.3, OSX 10.10, *) {67 escaped = string.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet) ?? string68 } else {69 let batchSize = 50 //一次转义的字符数70 var index = string.startIndex71 72 while index != string.endIndex {73 let startIndex = index74 let endIndex = index.advancedBy(batchSize, limit: string.endIndex)75 let range = startIndex..<endIndex76 77 let substring = string.substringWithRange(range)78 79 escaped += substring.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet) ?? substring80 81 index = endIndex82 }83 }84 85 return escaped86 }
原标题:iOS开发之Alamofire源码解析前奏
关键词:IOS
*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们:
admin#shaoqun.com
(#换成@)。