自己动手写一个 iOS 网络请求库(四)——快速文件上传

2015-5-29 / 阅读数:19337 / 分类: iOS & Swift

本篇文章是此系列文章的终结篇倒数第二篇,我们将一起给我们的网络请求库增加“快速文件上传”的功能。

HTTP 协议解析

找资料

我翻出了以前买的《图解 HTTP》:

找到第 46-47 页,“发送多种数据的多部分对象集合”:

multipart/form-data// HTTP 头 开始

Content-Type: multipart/form-data; boundary=PitayaUGl0YXlh

// HTTP 头 结束

// HTTP Body 开始

--PitayaUGl0YXlh

Content-Disposition: form-data; name="field1"

John Lui

--PitayaUGl0YXlh

Content-Disposition: form-data; name="text"; filename="file1.txt"

···[file1.txt 的数据]···

--PitayaUGl0YXlh--

// HTTP Body 结束

详解

HTTP 协议是一种非常基础的“字符串格式化约定”,本质上传输的依然是一堆字符,只是由于遵守了标准协议,后端的 HTTP 服务软件(Apache、nginx)和前端的浏览器、NSData、NSURLSession 等接口可以顺畅地交流。

在 HTTP 协议中,上传文件可以进行如下设置:

设定 Content-Type 头字段如下:Content-Type: multipart/form-data; boundary=PitayaUGl0YXlh

boundary 是我们自己指定的间隔符。

之后设定 HTTP Body 如下:--PitayaUGl0YXlh

Content-Disposition: form-data; name="field1"

John Lui

--PitayaUGl0YXlh

Content-Disposition: form-data; name="text"; filename="file1.txt"

···[file1.txt 的数据]···

--PitayaUGl0YXlh--

每个字段以 “--间隔符” 开头,最后总体以 “--间隔符--” 结尾。

换行

HTTP 协议中,换行必须用 \r\n,我尝试过只使用 \n 换行,系统会直接原封不动地发送这个换行,如果后端的 HTTP 服务器不支持这种容错的话,可能就会出问题,所以建议大家还是要遵守标准协议。

代码实现

构建 File 结构体

上传文件也是表单,也需要一个 name,所以我们需要构造一个 File 结构体,来描述要上传的文件:struct File {

let name: String!

let url: NSURL!

init(name: String, url: NSURL) {

self.name = name

self.url = url

}

}

上面代码中,我们使用 NSURL 来描述文件地址。

增加 files 类成员变量并初始化class NetworkManager {

let method: String!

let params: Dictionary

let callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void

// add files

var files: Array

let session = NSURLSession.sharedSession()

let url: String!

var request: NSMutableURLRequest!

var task: NSURLSessionTask!

// add files

init(url: String, method: String, params: Dictionary = Dictionary(), files: Array = Array(), 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

// add files

self.files = files

}

......

}

增加 boundary 类成员常量class NetworkManager {

let boundary = "PitayaUGl0YXlh"

......

}

更改 Content-Typeif self.files.count > 0 {

request.addValue("multipart/form-data; boundary=" + self.boundary, forHTTPHeaderField: "Content-Type")

} else if self.params.count > 0 {

request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")

}

修改 buildBody 函数func buildBody() {

let data = NSMutableData()

if self.files.count > 0 {

if self.method == "GET" {

NSLog("\n\n------------------------\nThe remote server may not accept GET method with HTTP body. But Pitaya will send it anyway.\n------------------------\n\n")

}

for (key, value) in self.params {

data.appendData("--\(self.boundary)\r\n".nsdata)

data.appendData("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".nsdata)

data.appendData("\(value.description)\r\n".nsdata)

}

for file in self.files {

data.appendData("--\(self.boundary)\r\n".nsdata)

data.appendData("Content-Disposition: form-data; name=\"\(file.name)\"; filename=\"\(file.url.description.lastPathComponent)\"\r\n\r\n".nsdata)

if let a = NSData(contentsOfURL: file.url) {

data.appendData(a)

data.appendData("\r\n".nsdata)

}

}

data.appendData("--\(self.boundary)--\r\n".nsdata)

} else if self.params.count > 0 && self.method != "GET" {

data.appendData(buildParams(self.params).nsdata)

}

request.HTTPBody = data

}

调整 Network.request 接口群,增加上传文件 APIstatic 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()

}

static func request(method: String, url: String, params: Dictionary, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {

let manager = NetworkManager(url: url, method: method, params: params, callback: callback)

manager.fire()

}

static func request(method: String, url: String, files: Array, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {

let manager = NetworkManager(url: url, method: method, files: files, callback: callback)

manager.fire()

}

static func request(method: String, url: String, params: Dictionary, files: Array, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {

let manager = NetworkManager(url: url, method: method, params: params, files: files, callback: callback)

manager.fire()

}

检验成果

增加一张图片用于上传文件测试:

测试代码如下:let file = File(name: "file", url: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Pitaya", ofType: "png")!)!)

Network.request("POST", url: "http://pitayaswift.sinaapp.com/pitaya.php", files: [file]) { (data, response, error) -> Void in

let string = NSString(data: data, encoding: NSUTF8StringEncoding) as! String

if string == "1" {

println("上传文件成功!")

}

}

运行项目,点击按钮,输出结果,成功!

快在哪里?

Alamofire 并不支持表单文件上传,似乎只支持流文件上传(不确定),故我之前在使用 Alamofire 的时候,是把二进制文件读出来之后进行 base64 编码,然后当做字符串字段传输的,除了体积会增大三分之一外,最严重的问题在于非常长的 HTTP 准备时间(开始发送数据包之前的处理时间),这期间还是阻塞的。实际测试,无论是 A5 处理器的 touch5 还是 A8 处理器的 iPhone6,500KB 的语音文件都需要接近 30S 的预处理时间。阻塞问题可以通过超线程方式解决,但是总体上传时间依然是非常长的,500 KB 的语音文件的预处理时间和网络传输时间几乎都一样长了。

快在哪里?采用 NSData 方式直接赋值给 HTTP Body,这种方式不会消耗任何预处理时间,当然也不会对主线程造成阻塞。而且传输的字符串的长度减少 25%,实际测试 500KB 语音文件上传速度从 57S 缩短为 21S,增速十分可观。

WRITTEN BY

程序员,Swift Contributor,正在写《iOS 可视化编程与 Auto Layout》。

评论:

BDSir

2016-10-26 11:08

能不能写一个HomeKit的demo供我们这些小白学习下

rose

2015-11-27 17:11

我用你的方法把图片上传给后台之后, 后台说要把图片赋给他指定的参数, 要怎么做啊 急 急 急!!!

rose

2015-11-27 17:07

请问上传图片的时候 图片的名字是随便起的么,后台需要一个参数来接收这些图片么?我怎么把这个图片放到后台的参数里面呢

2015-11-27 17:12

@rose:1. 名字是随便起的,这只是一个普通参数

2. 后台根据 name 来获取表单的参数,这是 HTTP 协议规定的

3. 使用我的方法就可以把图片放到请求中,至于后台怎么接收,你可以去问问后端程序员。。。

rose

2015-11-27 17:19

@JohnLui:后台说 他有一参数名photo 用来接收图片信息,一张图片的话就是一张图片信息, 多张就是一个数组,存放多张图片信息,我要怎么这多张图片放到 photo这个参数里面呢

2015-11-27 23:37

@rose:你的后端应该不懂 HTTP 协议的细节,只局限在他所使用的语言提供的功能里。我直接说解决方案吧:多张照片,一个名字,叫 name[]

rose

2015-11-30 13:43

@JohnLui:

非常感谢 我现在明白了 还了解了一点 http 协议  非常感谢 !!!

游客

2015-09-28 09:53

很不错,从里面学到了很多东西, 请问添加  摘要认证  怎么做? 最好在多些其它方式的证书认证,希望您在出一篇这样的问章。

3ks

喵~

2015-07-13 13:55

啥时候图解一下size classes喵~

发表评论:

昵称

邮件地址 (选填)

个人主页 (选填)

file上传代码 ios_自己动手写一个 iOS 网络请求库(四)——快速文件上传相关推荐

  1. 吕文翰 php,自己动手写一个 iOS 网络请求库(三)——降低耦合

    自己动手写一个 iOS 网络请求库(三)--降低耦合 2015-5-22 / 阅读数:16112 / 分类: iOS & Swift 本文中,我们将一起降低之前代码的耦合度,并使用适配器模式实 ...

  2. 自己动手写一个印钞机 第四章

    2019独角兽企业重金招聘Python工程师标准>>> 作者:阿布? 未经本人允许禁止转载 ipython notebook git版本 目录章节地址: 自己动手写一个印钞机 第一章 ...

  3. 写一个nodejs 网络请求

    我可以给你一个简单的nodejs网络请求的例子: const http = require('http'); const req = http.request({ hostname: 'example ...

  4. 自己动手写一个印钞机 第一章

    2019独角兽企业重金招聘Python工程师标准>>> 作者:阿布? 未经本人允许禁止转载 ipython notebook git版本 目录章节地址: 自己动手写一个印钞机 第一章 ...

  5. 自己动手写一个印钞机 第二章

    2019独角兽企业重金招聘Python工程师标准>>> 作者:阿布? 未经本人允许禁止转载 ipython notebook git版本 目录章节地址: 自己动手写一个印钞机 第一章 ...

  6. 自己动手写一个简单的bootloader

    自己动手写一个简单的bootloader 15年10月31日19:44:27 (一) start.S 写这一段代码前,先要清楚bootloader开始的时候都做什么了.无非就是硬件的初始化,我们想要写 ...

  7. java 手编线程池_死磕 java线程系列之自己动手写一个线程池

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. (手机横屏看源码更方便) 问题 (1)自己动手写一个线程池需要考虑哪些因素? (2)自己动手写 ...

  8. 学习较底层编程:动手写一个C语言编译器

    动手编写一个编译器,学习一下较为底层的编程方式,是一种学习计算机到底是如何工作的非常有效方法. 编译器通常被看作是十分复杂的工程.事实上,编写一个产品级的编译器也确实是一个庞大的任务.但是写一个小巧可 ...

  9. 自己动手写一个网盘?

    作者:桂花年糕仔,未经允许,不得转载! 持续更新中 文章目录 前言 第1章 环境搭建 第2章 上传测试及自写API 2.1 上传测试 2.2 自写API 2.3 上传的同时更新日志文件 第3章 red ...

  10. 自己动手写一个操作系统——MBR(2)

    前言 上篇文章<自己动手写一个操作系统--MBR(1)>,我们使用 dd 生成了一个 512 字节的镜像,并用 vim 将其最后两个字节修改成了 55 AA,以此来完成了 MBR 的构建. ...

最新文章

  1. 如何在Ubuntu 20.04上设置Python虚拟环境
  2. 防止酒后删库!日本人用 3 小时做了个酒精测试软件
  3. 晒晒公司整改后的拓扑图和设备
  4. 标题 穿越雷区 java_【蓝桥杯】穿越雷区-java语言描述
  5. Java微框架:不可忽视的新趋势--转载
  6. 不要再问了,数据库不建议上Docker
  7. Android踩坑日记:android7.0动态相机权限
  8. android cliptopadding java代码_android:clipToPadding属性的分析——以ListView的别样padding为例...
  9. 今天写的一个makefile,备份下
  10. 偷用计算机作文,偷玩电脑作文500字
  11. 两位“80后”女科学家分享:科研、坚持与热爱
  12. iOS - UITextField
  13. java游戏西门大官人_valueOf()方法的使用
  14. 找回计算机管理账户,怎样找回原来的电脑用户账号?
  15. S一文读懂应力集中与应力奇异
  16. 腾讯云域名转出转移码申请及转入阿里云全流程(图解)
  17. 原生JS制作缤纷色彩板
  18. 如何dismiss多个viewController
  19. c语言我喜欢你,【幻 仿】UC启动时的我喜欢你
  20. 1.1.人工智能的概念

热门文章

  1. java工商银行项目_工商银行聚合支付,java开发实现
  2. vite中antdesign-vue3的使用
  3. android按钮添加音效,Android中为按钮设置点击音效
  4. 【统计学】分类数据分析 相关分析 方差分析 比较 研究思路 spss
  5. 生意宝,淘宝,唯品会,58同城,去哪儿背后的赚钱生意经(转)
  6. switch语句的ns图怎么画_NS图绘制软件
  7. 基于ssm+vue的综合项目 健康体检管理系统-第十章-权限控制、图形报表
  8. linux 查看外网ip信息
  9. BFS和DFS搜索汇总(未完待续)
  10. 几百万数据量的Excel导出会内存溢出和卡顿?那是你没用对方法!