自己动手写一个iOS 网络请求库的三部曲[转]
代码示例:https://github.com/johnlui/Swift-On-iOS/blob/master/BuildYourHTTPRequestLibrary
开源项目:Pitaya,适合大文件上传的 HTTP 请求库:https://github.com/johnlui/Pitaya
本系列文章中,我们将尝试使用 NSURLSession 技术构建一个自己的网络请求库。
NSURLSession 简介
NSURLSession 是 iOS7 引入的新网络请求接口,在 WWDC2013 中有详细介绍,下面是描述其结构的一页 slides:
当应用在前台时,NSURLSession 跟 NSURLConnection 没有什么区别,但是在程序切换到后台之后 Background Session 就会更加灵活。
尝试 NSURLSession
准备工作
新建一个名为 BuildYourHTTPRequestLibrary 的单页面应用,在页面上居中放置一个按钮,名为 Request:
拖动绑定 Touch Up Inside 事件:
使用 NSURLSession
在 mainButtonBeTapped 函数内填充以下代码:
- @IBAction func mainButtonBeTapped(sender: AnyObject) {
- let session = NSURLSession.sharedSession()
- let request = NSURLRequest(URL: NSURL(string: "http://baidu.com")!)
- let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
- let string = NSString(data: data, encoding: NSUTF8StringEncoding)
- println(string)
- })
- task.resume()
- }
使用成功!
感受异步
异步
改写 mainButtonBeTapped 函数的代码:
- @IBAction func mainButtonBeTapped(sender: AnyObject) {
- let session = NSURLSession.sharedSession()
- let request = NSURLRequest(URL: NSURL(string: "http://baidu.com")!)
- let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
- println("just wait for 5 seconds!")
- sleep(5)
- let string = NSString(data: data, encoding: NSUTF8StringEncoding)
- println(string)
- })
- task.resume()
- }
再次尝试,两次打印之间间隔了五秒,主线程未阻塞,证明 NSURLSession 为异步执行。
阻塞
尝试多次点击,我们能够看到每五秒执行一次,直到全部执行完毕。
NSURLSession 采用的是 “异步阻塞” 模型,即所有请求在发出后都进入 2# 线程执行,在 2# 线程内部按照阻塞队列模式执行。
开源项目:Pitaya,适合大文件上传的 HTTP 请求库:https://github.com/johnlui/Pitaya
本章中,我们将一起尝试使用一个类来封装我们之前的代码,并尝试加入动态增加 HTTP 参数(params)的功能,之后封装出一个强大的接口。
基本封装
基础准备
新建一个 Swift 空文件,命名为 Network.swift,在里面写一个 Network 类,之后写一个静态方法 request():
- class Network{
- static func request() {
- let session = NSURLSession.sharedSession()
- let request = NSURLRequest(URL: NSURL(string: "http://baidu.com")!)
- let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
- println("just wait for 5 seconds!")
- sleep(5)
- let string = NSString(data: data, encoding: NSUTF8StringEncoding)
- println(string)
- })
- task.resume()
- }
- }
修改 ViewController 中的按钮函数:
- @IBAction func mainButtonBeTapped(sender: AnyObject) {
- Network.request()
- }
运行项目,点击按钮,效果和之前一致。
自定义 HTTP method 和 URL
修改 request() 方法,将 HTTP 方法和 URL 传进去:
- static func request(method: String, url: String) {
- let session = NSURLSession.sharedSession()
- let request = NSMutableURLRequest(URL: NSURL(string: url)!)
- request.HTTPMethod = method
- let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
- println("just wait for 5 seconds!")
- sleep(5)
- let string = NSString(data: data, encoding: NSUTF8StringEncoding)
- println(string)
- })
- task.resume()
- }
修改前面的函数调用:
- @IBAction func mainButtonBeTapped(sender: AnyObject) {
- Network.request("GET", url: "http://baidu.com")
- }
运行项目,点击按钮,效果和之前一致。
使用闭包处理请求结果
函数是 Swift 中的一等公民,闭包可以作为函数参数和返回值,十分强大。下面我们就用闭包来处理网络请求的返回值。修改 request() 方法,传递进去一个闭包:
- static func request(method: String, url: String, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
- let session = NSURLSession.sharedSession()
- let request = NSMutableURLRequest(URL: NSURL(string: url)!)
- request.HTTPMethod = method
- let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
- callback(data: data, response: response , error: error)
- })
- task.resume()
- }
在前面函数调用处使用闭包进行结果处理:
- @IBAction func mainButtonBeTapped(sender: AnyObject) {
- Network.request("GET", url: "http://baidu.com") { (data, response, error) -> Void in
- println("just wait for 5 seconds!")
- sleep(5)
- let string = NSString(data: data, encoding: NSUTF8StringEncoding)
- println(string)
- }
- }
运行项目,点击按钮,效果和之前一致。
动态增加 Params
GET 方法
GET 方法下,params 在经过 url encode 之后直接附在 URL 末尾发送给服务器。修改 request() 方法,传递进去一个 params 的字典:
- static func request(method: String, url: String, params: Dictionary = Dictionary(), callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
- ... ...
- }
为了处理 params,我们从 Alamofire 偷来他的 params 处理函数。如果是 GET 方法,那就把处理过的 params 增加到 URL 后面。Network 类的完整代码如下:
- class Network{
- static func request(method: String, url: String, params: Dictionary = Dictionary(), callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
- let session = NSURLSession.sharedSession()
- var newURL = url
- if method == "GET" {
- newURL += "?" + Network().buildParams(params)
- }
- let request = NSMutableURLRequest(URL: NSURL(string: newURL)!)
- request.HTTPMethod = method
- let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
- callback(data: data, response: response , error: error)
- })
- task.resume()
- }
- // 从 Alamofire 偷了三个函数
- func buildParams(parameters: [String: AnyObject]) -> String {
- var components: [(String, String)] = []
- for key in sorted(Array(parameters.keys), [(String, String)] {
- var components: [(String, String)] = []
- if let dictionary = value as? [String: AnyObject] {
- for (nestedKey, value) in dictionary {
- components += queryComponents("\(key)[\(nestedKey)]", value)
- }
- } else if let array = value as? [AnyObject] {
- for value in array {
- components += queryComponents("\(key)", value)
- }
- } else {
- components.extend([(escape(key), escape("\(value)"))])
- }
- return components
- }
- func escape(string: String) -> String {
- let legalURLCharactersToBeEscaped: CFStringRef = ":&=;+!@#$()',*"
- return CFURLCreateStringByAddingPercentEscapes(nil, string, nil, legalURLCharactersToBeEscaped, CFStringBuiltInEncodings.UTF8.rawValue) as String
- }
- }
修改前面的函数调用:
- @IBAction func mainButtonBeTapped(sender: AnyObject) {
- Network.request("GET", url: "http://pitayaswift.sinaapp.com/pitaya.php", params: ["get": "Network"]) { (data, response, error) -> Void in
- let string = NSString(data: data, encoding: NSUTF8StringEncoding)
- println(string)
- }
- }
http://pitayaswift.sinaapp.com/pitaya.php 是我部署的用于测试的服务端代码,会直接返回 ?get=ooxx 中的 ooxx。运行项目,点击按钮,查看效果:
POST 方法
POST 方法下有几个协议可供选择,此处没有文件上传,我们采用较简单的 application/x-www-form-urlencoded 方式发送请求。request() 方法增加一些代码:
- static func request(method: String, url: String, params: Dictionary = Dictionary(), callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
- let session = NSURLSession.sharedSession()
- var newURL = url
- if method == "GET" {
- newURL += "?" + Network().buildParams(params)
- }
- let request = NSMutableURLRequest(URL: NSURL(string: newURL)!)
- request.HTTPMethod = method
- if method == "POST" {
- request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
- request.HTTPBody = Network().buildParams(params).dataUsingEncoding(NSUTF8StringEncoding)
- }
- let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
- callback(data: data, response: response , error: error)
- })
- task.resume()
- }
修改前面的函数调用:
- @IBAction func mainButtonBeTapped(sender: AnyObject) {
- Network.request("POST", url: "http://pitayaswift.sinaapp.com/pitaya.php", params: ["post": "Network"]) { (data, response, error) -> Void in
- let string = NSString(data: data, encoding: NSUTF8StringEncoding)
- println(string)
- }
- }
使用 POST 方式发送请求,同样服务端会返回 key 为 post 的 value 的值。运行项目,点击按钮,结果和前面 GET 方法的结果一致。
至此,接口封装完成!
开源项目:Pitaya,适合大文件上传的 HTTP 请求库:https://github.com/johnlui/Pitaya
本文中,我们将一起降低之前代码的耦合度,并使用适配器模式实现一层独立于底层结构的网络 API,造一个真正的网络请求“库”。
降低耦合度
如何降低耦合度
现在的清汤挂面式的代码虽然便于理解,但是功能单一,代码杂乱。我们一起来分析 NSURLSession 的使用过程:
构造 NSURLRequest
确定 URL
确定 HTTP 方法(GET、POST 等)
添加特定的 HTTP 头
填充 HTTP Body
驱动 session.dataTaskWithRequest 方法,开始请求
具体实施
在 Network 下另外新建一个 NetworkManager 类,将 URL、params、files 等设为成员变量,让他们在构造函数中初始化:
- class NetworkManager {
- let method: String!
- let params: Dictionary let callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void
- let session = NSURLSession.sharedSession()
- let url: String!
- var request: NSMutableURLRequest!
- var task: NSURLSessionTask!
- init(url: String, method: String, params: Dictionary = Dictionary(), callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
- self.url = url
- self.request = NSMutableURLRequest(URL: NSURL(string: url)!)
- self.method = method
- self.params = params
- self.callback = callback
- }
- }
之后,将上面分析的
1. 确定 URL
2. 确定 HTTP 方法(GET、POST 等)
3. 添加特定的 HTTP 头
4. 填充 HTTP Body
前三步封装到一个 function 中,最后一步封装到一个 function 中,然后把驱动 session.dataTaskWithRequest 的代码封装到一个 function 中:
- func buildRequest() {
- if self.method == "GET" && self.params.count > 0 {
- self.request = NSMutableURLRequest(URL: NSURL(string: url + "?" + buildParams(self.params))!)
- }
- request.HTTPMethod = self.method
- if self.params.count > 0 {
- request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
- }
- }
- func buildBody() {
- if self.params.count > 0 && self.method != "GET" {
- request.HTTPBody = buildParams(self.params).nsdata
- }
- }
- func fireTask() {
- task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
- self.callback(data: data, response: response, error: error)
- })
- task.resume()
- }
之后使用一个统一的方法来驱动上面三个 function,完成请求:
- func fire() {
- buildRequest()
- buildBody()
- fireTask()
- }
同时,不要忘了那三个 parse params 的从 Alamofire 偷来的函数哦,也要放到这个类里面。至此,降低耦合的工作基本完成,接下来我们开始封装“网络API”。
使用适配器模式封装“网络API”
理解适配器模式
适配器模式是设计模式中的一种,很容易理解:我的 APP 需要一个获取某一个 URL 返回的字符串的功能,我现在选择的是 Alamofire,但是正在发展的 Pitaya 看起来不错,我以后想替换成 Pitaya,所以我封装了一层我自己的网络接口,用来屏蔽底层细节,到时候只需要修改这个类,不需要再深入项目中改那么多接口调用了。
适配器模式听起来高大上,其实这是我们在日常编码中非常常用的设计模式。
Do it!
修改 Network 类的代码为:
- class Network{
- static func request(method: String, url: String, params: Dictionary = Dictionary(), callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
- let manager = NetworkManager(url: url, method: method, params: params, callback: callback)
- manager.fire()
- }
- }
搞定!
封装多级接口
不带 params 的接口:
- static func request(method: String, url: String, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
- let manager = NetworkManager(url: url, method: method, callback: callback)
- manager.fire()
- }
两个 get 接口(带与不带 params):
- static func get(url: String, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
- let manager = NetworkManager(url: url, method: "GET", callback: callback)
- manager.fire()
- }
- static func get(url: String, params: Dictionary, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
- let manager = NetworkManager(url: url, method: "GET", params: params, callback: callback)
- manager.fire()
- }
两个 post 接口(带与不带 params):
- static func post(url: String, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
- let manager = NetworkManager(url: url, method: "POST", callback: callback)
- manager.fire()
- }
- static func post(url: String, params: Dictionary, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
- let manager = NetworkManager(url: url, method: "POST", params: params, callback: callback)
- manager.fire()
- }
测试接口
修改 ViewController 中的调用代码,测试多级 API:
- @IBAction func mainButtonBeTapped(sender: AnyObject) {
- let url = "http://pitayaswift.sinaapp.com/pitaya.php"
- Network.post(url, callback: { (data, response, error) -> Void in
- println("POST 1 请求成功")
- })
- Network.post(url, params: ["post": "POST Network"], callback: { (data, response, error) -> Void in
- let string = NSString(data: data, encoding: NSUTF8StringEncoding) as! String
- println("POST 2 请求成功 " + string)
- })
- Network.get(url, callback: { (data, response, error) -> Void in
- println("GET 1 请求成功")
- })
- Network.get(url, params: ["get": "POST Network"], callback: { (data, response, error) -> Void in
- let string = NSString(data: data, encoding: NSUTF8StringEncoding) as! String
- println("GET 2 请求成功 " + string)
- })
- Network.request("GET", url: url, params: ["get": "Request Network"]) { (data, response, error) -> Void in
- let string = NSString(data: data, encoding: NSUTF8StringEncoding) as! String
- println("Request 请求成功 " + string)
- }
- }
运行项目,点击按钮,查看效果:
多级 API 封装成功!
自己动手写一个iOS 网络请求库的三部曲[转]的更多相关文章
- 【转载】一步一步搭建自己的iOS网络请求库
一步一步搭建自己的iOS网络请求库(一) 大家好,我是LastDay,很久没有写博客了,这周会分享一个的HTTP请求库的编写经验. 简单的介绍 介绍一下,NSURLSession是iOS7中新的网络接 ...
- 造轮子 | 怎样设计一个面向协议的 iOS 网络请求库
近期开源了一个面向协议设计的网络请求库 MBNetwork,基于 Alamofire 和 ObjectMapper 实现,目的是简化业务层的网络请求操作. 须要干些啥 对于大部分 App 而言,业务层 ...
- LXNetwork – 基于AF3.0封装的iOS网络请求库
本框架实现思路与YTKNetwork和RTNetworking类似,相当于一个简单版,把每一个网络请求封装成对象.使用LXNetwork,你的每一个请求都需要继承LXBaseRequest类,通过覆盖 ...
- Retrofit网络请求库应用02——json解析
PS:上一篇写了Retrofit网络请求库的简单使用,仅仅是获取百度的源码,来证明连接成功,这篇讲解如何解析JSON数据,该框架不再是我们之前自己写的那样用JsonArray等来解析,这些东西,我们都 ...
- 浅论Android网络请求库——android-async-http
在iOS开发中有大名鼎鼎的ASIHttpRequest库,用来处理网络请求操作,今天要介绍的是一个在Android上同样强大的网络请求库android-async-http,目前非常火的应用Insta ...
- swift中第三方网络请求库Alamofire的安装与使用
swift中第三方网络请求库Alamofire的安装与使用 Alamofire是swift中一个比较流行的网络请求库:https://github.com/Alamofire/Alamofire.下面 ...
- [转]Android各大网络请求库的比较及实战
自己学习android也有一段时间了,在实际开发中,频繁的接触网络请求,而网络请求的方式很多,最常见的那么几个也就那么几个.本篇文章对常见的网络请求库进行一个总结. HttpUrlConnection ...
- Android之网络请求库
自己学习android也有一段时间了,在实际开发中,频繁的接触网络请求,而网络请求的方式很多,最常见的那么几个也就那么几个.本篇文章对常见的网络请求库进行一个总结. HttpUrlConnection ...
- Android进阶笔记02:Android 网络请求库的比较及实战(二)
一.Volley 既然在android2.2之后不建议使用HttpClient,那么有没有一个库是android2.2及以下版本使用HttpClient,而android2.3及以上版本 ...
随机推荐
- 怒刷BZOJ记录(二)1038~10xx
我实在是太弱了...不滚粗只能刷BZOJ了...这里来记录每天刷了什么题吧. 2015-8-13: 正式开始! 1030[JSOI2007]文本生成器 | ...
- mkfs 的使用
使用方法: [root@localhost beinan]# mkfs -t 文件系统 存储设备 注:这里的文件系统是要指定的,比如 ext3 :reiserfs :ext2 :fat32 :msd ...
- (转载)mysql书籍
(转载)http://blog.csdn.net/symdfbb/article/details/7636332 MySQL技术内幕 mysql使用大全,可以说方方面面都包括了.认真研读大概一本就差不 ...
- git bash【初级入门篇】
最近公司打算使用git代替之前的svn版本控制工具,趁此机会打算好好学学git,这个号称当今世界最牛的分布式版本控制工具. 一.[git和svn的主要区别] 1.去中心化 svn以及微软的TFS均采用 ...
- Java---基于TCP协议的相互即时通讯小程序
这是几年前,新浪的一个面试题~要求是3天之内实现~ 通过TCP 协议,建立一个服务器端. 通过配置服务器端的IP和端口: 客户端之间就可以相互通讯~ 上线了全部在线用户会收到你上线的通知. 下线了全部 ...
- MT9M021/MT9M031总结
MT9M021/MT9m031在低光照度下和捕捉移动场景有非常优异的表现,属于近红外摄像头, S1: Aptina's MT9M021/MT9M031 sensor is capable of a m ...
- SRM 601(1-250pt,500pt)
DIV1 250pt 题意:有很多袋子,里面装有苹果和橘子(也可能没有),给出每个袋子里有多少个苹果,多少个橘子.如果每个袋子里含有水果的总数都不小于x个,则可以从每个袋子里都拿出x个水果(拿出苹果和 ...
- nginx做负载均衡器以及proxy缓存配置 - SegmentFault
nginx做负载均衡器以及proxy缓存配置 - SegmentFault nginx做负载均衡器以及proxy缓存配置
- 987654321 problem - SGU 107(找规律)
题目大意:求n位数的平方的后几位结果是987654321的个数是多少. 分析:刚看到这道题的时候怀疑过有没有这样的数,于是暴力跑了一下,发现还真有,9位的数有8个,如下: i=111111111, i ...
- An Easy Problem?! - POJ 2826(求面积)
题目大意:有两块木板交叉起来接雨水,问最多能接多少. 分析:题目描述很简单,不过有些细节还是需要注意到,如下图几种情况: #include<stdio.h> #include< ...