iOS开发中文件的上传和下载功能的基本实现
文件的上传
说明:文件上传使用的时post请求,通常把要上传的数据保存在请求体中。本文介绍如何不借助第三方框架实现ios开发中得文件上传。
由于过程较为复杂,因此本文只贴出部分关键代码。
主控制器的关键代码:
yyviewcontroller.m
#import "yyviewcontroller.h"
#define yyencode(str) [str datausingencoding:nsutf8stringencoding]
@interface yyviewcontroller ()
@end
@implementation yyviewcontroller
- (void)viewdidload
{
[super viewdidload];
// do any additional setup after loading the view, typically from a nib.
}
- (void)upload:(nsstring *)name filename:(nsstring *)filename mimetype:(nsstring *)mimetype data:(nsdata *)data parmas:(nsdictionary *)params
{
// 文件上传
nsurl *url = [nsurl urlwithstring:@"http://192.168.1.200:8080/yyserver/upload"];
nsmutableurlrequest *request = [nsmutableurlrequest requestwithurl:url];
request.httpmethod = @"post";
// 设置请求体
nsmutabledata *body = [nsmutabledata data];
/***************文件参数***************/
// 参数开始的标志
[body appenddata:yyencode(@"--yy\r\n")];
// name : 指定参数名(必须跟服务器端保持一致)
// filename : 文件名
nsstring *disposition = [nsstring stringwithformat:@"content-disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename];
[body appenddata:yyencode(disposition)];
nsstring *type = [nsstring stringwithformat:@"content-type: %@\r\n", mimetype];
[body appenddata:yyencode(type)];
[body appenddata:yyencode(@"\r\n")];
[body appenddata:data];
[body appenddata:yyencode(@"\r\n")];
/***************普通参数***************/
[params enumeratekeysandobjectsusingblock:^(id key, id obj, bool *stop) {
// 参数开始的标志
[body appenddata:yyencode(@"--yy\r\n")];
nsstring *disposition = [nsstring stringwithformat:@"content-disposition: form-data; name=\"%@\"\r\n", key];
[body appenddata:yyencode(disposition)];
[body appenddata:yyencode(@"\r\n")];
[body appenddata:yyencode(obj)];
[body appenddata:yyencode(@"\r\n")];
}];
/***************参数结束***************/
// yy--\r\n
[body appenddata:yyencode(@"--yy--\r\n")];
request.httpbody = body;
// 设置请求头
// 请求体的长度
[request setvalue:[nsstring stringwithformat:@"%zd", body.length] forhttpheaderfield:@"content-length"];
// 声明这个post请求是个文件上传
[request setvalue:@"multipart/form-data; boundary=yy" forhttpheaderfield:@"content-type"];
// 发送请求
[nsurlconnection sendasynchronousrequest:request queue:[nsoperationqueue mainqueue] completionhandler:^(nsurlresponse *response, nsdata *data, nserror *connectionerror) {
if (data) {
nsdictionary *dict = [nsjsonserialization jsonobjectwithdata:data options:nsjsonreadingmutableleaves error:nil];
nslog(@"%@", dict);
} else {
nslog(@"上传失败");
}
}];
}
- (void)touchesbegan:(nsset *)touches withevent:(uievent *)event
{
// socket 实现断点上传
//apache-tomcat-6.0.41/conf/web.xml 查找 文件的 mimetype
// uiimage *image = [uiimage imagenamed:@"test"];
// nsdata *filedata = uiimagepngrepresentation(image);
// [self upload:@"file" filename:@"test.png" mimetype:@"image/png" data:filedata parmas:@{@"username" : @"123"}];
// 给本地文件发送一个请求
nsurl *fileurl = [[nsbundle mainbundle] urlforresource:@"itcast.txt" withextension:nil];
nsurlrequest *request = [nsurlrequest requestwithurl:fileurl];
nsurlresponse *repsonse = nil;
nsdata *data = [nsurlconnection sendsynchronousrequest:request returningresponse:&repsonse error:nil];
// 得到mimetype
nslog(@"%@", repsonse.mimetype);
[self upload:@"file" filename:@"itcast.txt" mimetype:repsonse.mimetype data:data parmas:@{
@"username" : @"999",
@"type" : @"xml"}];
}
@end
补充说明:
文件上传请求数据格式
部分文件的mimetype
多线程断点下载
说明:本文介绍多线程断点下载。项目中使用了苹果自带的类,实现了同时开启多条线程下载一个较大的文件。因为实现过程较为复杂,所以下面贴出完整的代码。
实现思路:下载开始,创建一个和要下载的文件大小相同的文件(如果要下载的文件为100m,那么就在沙盒中创建一个100m的文件,然后计算每一段的下载量,开启多条线程下载各段的数据,分别写入对应的文件部分)。
项目中用到的主要类如下:
完成的实现代码如下:
主控制器中的代码:
#import "yyviewcontroller.h"
#import "yyfilemultidownloader.h"
@interface yyviewcontroller ()
@property (nonatomic, strong) yyfilemultidownloader *filemultidownloader;
@end
@implementation yyviewcontroller
- (yyfilemultidownloader *)filemultidownloader
{
if (!_filemultidownloader) {
_filemultidownloader = [[yyfilemultidownloader alloc] init];
// 需要下载的文件远程url
_filemultidownloader.url = @"http://192.168.1.200:8080/mjserver/resources/jre.zip";
// 文件保存到什么地方
nsstring *caches = [nssearchpathfordirectoriesindomains(nscachesdirectory, nsuserdomainmask, yes) lastobject];
nsstring *filepath = [caches stringbyappendingpathcomponent:@"jre.zip"];
_filemultidownloader.destpath = filepath;
}
return _filemultidownloader;
}
- (void)viewdidload
{
[super viewdidload];
}
- (void)touchesbegan:(nsset *)touches withevent:(uievent *)event
{
[self.filemultidownloader start];
}
@end
自定义一个基类
yyfiledownloader.h文件
#import <foundation/foundation.h>
@interface yyfiledownloader : nsobject
{
bool _downloading;
}
/**
* 所需要下载文件的远程url(连接服务器的路径)
*/
@property (nonatomic, copy) nsstring *url;
/**
* 文件的存储路径(文件下载到什么地方)
*/
@property (nonatomic, copy) nsstring *destpath;
/**
* 是否正在下载(有没有在下载, 只有下载器内部才知道)
*/
@property (nonatomic, readonly, getter = isdownloading) bool downloading;
/**
* 用来监听下载进度
*/
@property (nonatomic, copy) void (^progresshandler)(double progress);
/**
* 开始(恢复)下载
*/
- (void)start;
/**
* 暂停下载
*/
- (void)pause;
@end
yyfiledownloader.m文件
#import "yyfiledownloader.h"
@implementation yyfiledownloader
@end
下载器类继承自yyfiledownloader这个类
yyfilesingdownloader.h文件
#import "yyfiledownloader.h"
@interface yyfilesingledownloader : yyfiledownloader
/**
* 开始的位置
*/
@property (nonatomic, assign) long long begin;
/**
* 结束的位置
*/
@property (nonatomic, assign) long long end;
@end
yyfilesingdownloader.m文件
#import "yyfilesingledownloader.h"
@interface yyfilesingledownloader() <nsurlconnectiondatadelegate>
/**
* 连接对象
*/
@property (nonatomic, strong) nsurlconnection *conn;
/**
* 写数据的文件句柄
*/
@property (nonatomic, strong) nsfilehandle *writehandle;
/**
* 当前已下载数据的长度
*/
@property (nonatomic, assign) long long currentlength;
@end
@implementation yyfilesingledownloader
- (nsfilehandle *)writehandle
{
if (!_writehandle) {
_writehandle = [nsfilehandle filehandleforwritingatpath:self.destpath];
}
return _writehandle;
}
/**
* 开始(恢复)下载
*/
- (void)start
{
nsurl *url = [nsurl urlwithstring:self.url];
// 默认就是get请求
nsmutableurlrequest *request = [nsmutableurlrequest requestwithurl:url];
// 设置请求头信息
nsstring *value = [nsstring stringwithformat:@"bytes=%lld-%lld", self.begin + self.currentlength, self.end];
[request setvalue:value forhttpheaderfield:@"range"];
self.conn = [nsurlconnection connectionwithrequest:request delegate:self];
_downloading = yes;
}
/**
* 暂停下载
*/
- (void)pause
{
[self.conn cancel];
self.conn = nil;
_downloading = no;
}
#pragma mark - nsurlconnectiondatadelegate 代理方法
/**
* 1. 当接受到服务器的响应(连通了服务器)就会调用
*/
- (void)connection:(nsurlconnection *)connection didreceiveresponse:(nsurlresponse *)response
{
}
/**
* 2. 当接受到服务器的数据就会调用(可能会被调用多次, 每次调用只会传递部分数据)
*/
- (void)connection:(nsurlconnection *)connection didreceivedata:(nsdata *)data
{
// 移动到文件的尾部
[self.writehandle seektofileoffset:self.begin + self.currentlength];
// 从当前移动的位置(文件尾部)开始写入数据
[self.writehandle writedata:data];
// 累加长度
self.currentlength += data.length;
// 打印下载进度
double progress = (double)self.currentlength / (self.end - self.begin);
if (self.progresshandler) {
self.progresshandler(progress);
}
}
/**
* 3. 当服务器的数据接受完毕后就会调用
*/
- (void)connectiondidfinishloading:(nsurlconnection *)connection
{
// 清空属性值
self.currentlength = 0;
// 关闭连接(不再输入数据到文件中)
[self.writehandle closefile];
self.writehandle = nil;
}
/**
* 请求错误(失败)的时候调用(请求超时\断网\没有网, 一般指客户端错误)
*/
- (void)connection:(nsurlconnection *)connection didfailwitherror:(nserror *)error
{
}
@end
设计多线程下载器(利用hmfilemultidownloader能开启多个线程同时下载一个文件)
一个多线程下载器只下载一个文件
yyfilemultidownloader.h文件
#import "yyfiledownloader.h"
@interface yyfilemultidownloader : yyfiledownloader
@end
yyfilemultidownloader.m文件
#import "yyfilemultidownloader.h"
#import "yyfilesingledownloader.h"
#define yymaxdownloadcount 4
@interface yyfilemultidownloader()
@property (nonatomic, strong) nsmutablearray *singledownloaders;
@property (nonatomic, assign) long long totallength;
@end
@implementation yyfilemultidownloader
- (void)getfilesize
{
nsmutableurlrequest *request = [nsmutableurlrequest requestwithurl:[nsurl urlwithstring:self.url]];
request.httpmethod = @"head";
nsurlresponse *response = nil;
#warning 这里要用异步请求
[nsurlconnection sendsynchronousrequest:request returningresponse:&response error:nil];
self.totallength = response.expectedcontentlength;
}
- (nsmutablearray *)singledownloaders
{
if (!_singledownloaders) {
_singledownloaders = [nsmutablearray array];
// 获得文件大小
[self getfilesize];
// 每条路径的下载量
long long size = 0;
if (self.totallength % yymaxdownloadcount == 0) {
size = self.totallength / yymaxdownloadcount;
} else {
size = self.totallength / yymaxdownloadcount + 1;
}
// 创建n个下载器
for (int i = 0; i<yymaxdownloadcount; i++) {
yyfilesingledownloader *singledownloader = [[yyfilesingledownloader alloc] init];
singledownloader.url = self.url;
singledownloader.destpath = self.destpath;
singledownloader.begin = i * size;
singledownloader.end = singledownloader.begin + size - 1;
singledownloader.progresshandler = ^(double progress){
nslog(@"%d --- %f", i, progress);
};
[_singledownloaders addobject:singledownloader];
}
// 创建一个跟服务器文件等大小的临时文件
[[nsfilemanager defaultmanager] createfileatpath:self.destpath contents:nil attributes:nil];
// 让self.destpath文件的长度是self.totallengt
nsfilehandle *handle = [nsfilehandle filehandleforwritingatpath:self.destpath];
[handle truncatefileatoffset:self.totallength];
}
return _singledownloaders;
}
/**
* 开始(恢复)下载
*/
- (void)start
{
[self.singledownloaders makeobjectsperformselector:@selector(start)];
_downloading = yes;
}
/**
* 暂停下载
*/
- (void)pause
{
[self.singledownloaders makeobjectsperformselector:@selector(pause)];
_downloading = no;
}
@end
补充说明:如何获得将要下载的文件的大小?