Hello, World!

断点下载与文件上传

字数统计: 1.8k阅读时长: 9 min
2017/06/20 Share

本篇博文的所有网络操作的对象都是一张图片。

普通下载

使用NSURLSessionDownloadTask实现。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@interface DownloadController ()<NSURLSessionDownloadDelegate>

@end

static NSString *const ImageURL = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1603118011167&di=fd9df24b9d887970aed9a6ec147d40da&imgtype=0&src=http%3A%2F%2Fimg.pconline.com.cn%2Fimages%2Fupload%2Fupc%2Ftx%2Fwallpaper%2F1209%2F05%2Fc0%2F13630426_1346827472062.jpg";

@implementation DownloadController {
NSString *_fullPath;
}

- (void)viewDidLoad {
[super viewDidLoad];

NSString *fileName = @"smile.png"; // 文件名称 通常需要通过md5生成一个唯一文件名
NSString *docuPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
_fullPath = [docuPath stringByAppendingPathComponent:fileName];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self loadImage];
}

- (void)loadImage {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:ImageURL]];
request.HTTPMethod = @"GET";
// ephemeralSessionConfiguration 不带缓存 defaultSessionConfiguration带缓存
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
// NSURLSessionDownloadTask把数据直接下载到沙盒下tmp目录中
NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request];
[task resume];
}

#pragma mark - NSURLSessionDelegate
//1 响应头 这次网络数据的属性(下载的总数据大小 content-Length, Content-Type)
// 响应的过滤
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {

completionHandler(NSURLSessionResponseAllow);
}

// 下载过程 下载的进度 接收数据回掉
// 下载的数据自动写入沙盒下tmp目录中,未下载完的文件会一直保存,下载完成的话,会自动从目录中移除。
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
// bytesWritten 当前包收到的数据 totalBytesWritten已经接受的数据 totalBytesExpectedToWrite总数据大小
NSLog(@"当前包收到的数据:%lld", bytesWritten);
NSLog(@"已经接收到的总数据:%lld", totalBytesWritten);
NSLog(@"需接收的总数据:%lld", totalBytesExpectedToWrite);
}

// 文件下载完成
// @param location 文件下载到沙盒下tmp目录中,系统会自动清理
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
NSError *error = nil;
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:_fullPath] error:&error];
}

// 网络任务结束
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {

}

运行代码之后,在沙盒的Document目录中会有smile.png的图片,表示下载成功了。

注意: NSURLSessionDownloadTask下载的数据自动写入沙盒下tmp目录中,未下载完的文件会一直保存,下载完成的话,会自动移除。所以一定要在下载完成时刻把文件移动到目标路径下,否则文件就被移除了。

断点下载

断点下载的流程如下图
network1

断点下载原理:在网络请求之前,先获取本地已下载数据的size,配置请求头Range。如下代码所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@interface BreakDownloadController ()<NSURLSessionTaskDelegate>

@end

static NSString *const ImageURL = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1603118011167&di=fd9df24b9d887970aed9a6ec147d40da&imgtype=0&src=http%3A%2F%2Fimg.pconline.com.cn%2Fimages%2Fupload%2Fupc%2Ftx%2Fwallpaper%2F1209%2F05%2Fc0%2F13630426_1346827472062.jpg";

@implementation BreakDownloadController {
NSString *_filePathDocument;
NSOutputStream *_outputStream;
}

- (void)viewDidLoad {
[super viewDidLoad];

NSString *fileName = @"smile";
NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
_filePathDocument = [filePath stringByAppendingPathComponent:fileName];
_outputStream = [[NSOutputStream alloc] initToFileAtPath:_filePathDocument append:YES];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self loadImage];
}

- (void)loadImage {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:ImageURL]];
request.HTTPMethod = @"GET";
NSDictionary *fileInfo = [[NSFileManager defaultManager] attributesOfItemAtPath:_filePathDocument error:nil];
long filesize = [[fileInfo objectForKey:NSFileSize] longValue];
[request setValue:[NSString stringWithFormat:@"bytes=%ld-", filesize] forHTTPHeaderField:@"Range"];

NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
[task resume];
}

#pragma mark - NSURLSessionDelegate
// 响应的过滤
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
[_outputStream open];
completionHandler(NSURLSessionResponseAllow);
}

// 数据接收
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
[_outputStream write:[data bytes] maxLength:data.length];
}

// 完成
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error {
[_outputStream close];
}

@end

注意一: NSURLSessionDataTask下载的数据是在内存中的。需要通过NSOutputStream写入沙盒。(不要NSData直接写入沙盒,性能较差)
注意二: 使用NSFileManager获取沙盒文件的size。

文件上传

文件上传的核心是表单的拼接,如下是表单的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
--Boundary+AA1C6D73B71C103F
Content-Disposition: form-data; name="paramKey1"

paramValue1
--Boundary+AA1C6D73B71C103F
Content-Disposition: form-data; name="paramKey2"

paramValue2
--Boundary+AA1C6D73B71C103F
Content-Disposition: form-data; name="image"; filename="customName"
Content-Type: image/png

PNG;��^3����)or�!�.st�n��x(+9'���...

表单由四部分组成:
  1,开始和结束边界,如–Boundary+28FBA24634750C95–
  2,请求参数,如Content-Disposition: form-data;name=”paramKey1” paramValue1
  3,上传文件的属性,如Content-Type: image/png
  4,上传文件的数据,如PNG;��^3����)or�!�.st�n��x(+9’���
  
二进制形式的表单就是请求体。

  • 使用NSMutableURLRequest.HTTPBody属性设置请求体,如下代码所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 文件上传
- (void)uploadImage {

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:UploadImageURL]];
// 表单拼接
NSString *bounary = @"******"; // 分界线
// 一 配置请求头
[request setValue:[NSString stringWithFormat:@"multipart/form-data;charset=utf-8;boundary=%@", bounary] forHTTPHeaderField:@"Content-Type"];
// 二 配置请求体
NSMutableData *bodyData = [NSMutableData data];
// 1,开始边界
NSString *beginBoundary = [NSString stringWithFormat:@"--%@\r\n", bounary];
[bodyData appendData:[beginBoundary dataUsingEncoding:NSUTF8StringEncoding]];
// 2,属性 name和服务的name要匹配,相当于服务获取图片的key
// filename 服务器图片文件命名
NSString *serverFileKey = @"image";
NSString *serverFileName = @"101.png";
NSString *serverContentTypes = @"image/png";

NSMutableString *string = [NSMutableString string];
[string appendFormat:@"Content-Disposition:form-data; name=\"%@\"; filename=\"%@\" \r\n", serverFileKey, serverFileName];
[string appendFormat:@"Content-Type: %@\r\n", serverContentTypes];
[string appendFormat:@"\r\n"];
[bodyData appendData:[string dataUsingEncoding:NSUTF8StringEncoding]];

// 3,上传文件数据
UIImage *image = [UIImage imageNamed:@"demo.png"];
NSData *imageData = UIImagePNGRepresentation(image);
[bodyData appendData:imageData];

// 4,结束边界
NSString *endBoundary = [NSString stringWithFormat:@"\r\n--%@", bounary];
[bodyData appendData:[endBoundary dataUsingEncoding:NSUTF8StringEncoding]];

request.HTTPBody = bodyData;
request.HTTPMethod = @"POST";

NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
[task resume];
}

// 进度
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{

NSLog(@"didSendBodyData:: %lld--%lld--%lld", bytesSent, totalBytesSent, totalBytesExpectedToSend);
}

// 完成
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
NSLog(@"上传完成");
}

表单注意点:一,每个部分都是以\n为结束点。二,属性中的name和服务的name要匹配。

  • 使用NSMutableURLRequest.HTTPBodyStream属性设置请求体,如下代码所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@interface InputStreamUploadController ()<NSURLSessionDelegate>

@end

#define UploadImageURL @"http://www.8pmedu.com/themes/jianmo/img/upload.php"

@implementation InputStreamUploadController

- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColorRandomColor;

[self inputStream];
}

- (void)inputStream {

NSString *bodyStr = @"versions_id=1&system_type=1";
NSInputStream *inputsteam = [[NSInputStream alloc] initWithData:[bodyStr dataUsingEncoding:NSUTF8StringEncoding]];
[inputsteam open];

uint8_t buffer[256];
memset(buffer, 0, 256);// 清空该段内存
[inputsteam read:buffer maxLength:9];
NSLog(@"++++%s", buffer);

memset(buffer, 0, 256);// 清空该段内存
[inputsteam read:buffer maxLength:3];
NSLog(@"----%s", buffer);
[inputsteam close];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self uploadImage];
}

- (void)uploadImage {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:UploadImageURL]];
// 配置请求头和请求体
NSString *bounary = @"******"; // 分界线
// 一 配置请求头
[request setValue:[NSString stringWithFormat:@"multipart/form-data;charset=utf-8;boundary=%@", bounary] forHTTPHeaderField:@"Content-Type"];
// 二 配置请求体
NSMutableData *bodyData = [NSMutableData data];
// 1 开始边界
NSString *beginBoundary = [NSString stringWithFormat:@"--%@\r\n", bounary];
[bodyData appendData:[beginBoundary dataUsingEncoding:NSUTF8StringEncoding]];
// 2 属性 name和服务的name要匹配,相当于获取服务图片的key
// filename 服务器图片文件命名
NSString *serverFileKey = @"image";
NSString *serverFileName = @"101.png";
NSString *serverContentTypes = @"image/png";

NSMutableString *string = [NSMutableString string];
[string appendFormat:@"Content-Disposition:form-data; name=\"%@\"; filename=\"%@\" \r\n", serverFileKey, serverFileName];
[string appendFormat:@"Content-Type: %@\r\n", serverContentTypes];
[string appendFormat:@"\r\n"];
[bodyData appendData:[string dataUsingEncoding:NSUTF8StringEncoding]];

// 3 文件数据
UIImage *image = [UIImage imageNamed:@"1"];
NSData *imageData = UIImagePNGRepresentation(image);
[bodyData appendData:imageData];

// 4 结束边界
NSString *endBoundary = [NSString stringWithFormat:@"\r\n--%@", bounary];
[bodyData appendData:[endBoundary dataUsingEncoding:NSUTF8StringEncoding]];

NSInputStream *inputsteam = [[NSInputStream alloc] initWithData:bodyData];
[request setHTTPBodyStream:inputsteam]; // 请求的时候,系统底层会对NSInputStream执行read操作

NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];

NSURLSessionTask *task = [session dataTaskWithRequest:request];
[task resume];
}

#pragma mark - NSURLSessionDelegate
// 进度
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
NSLog(@"didSendBodyData:: %lld--%lld--%lld", bytesSent, totalBytesSent, totalBytesExpectedToSend);
}

// 完成
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSLog(@"over %@", error);
}

@end

** 文件上传的NSURLRequest实例,需要配置如下属性:**
1,URL
2,HTTPMethod
3,setValue:forHTTPHeaderField:
4,HTTPBody/HTTPBodyStream

CATALOG
  1. 1. 普通下载
  2. 2. 断点下载
  3. 3. 文件上传