本篇博文的所有网络操作的对象都是一张图片。
普通下载
使用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]; }
//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目录中,未下载完的文件会一直保存,下载完成的话,会自动移除。所以一定要在下载完成时刻把文件移动到目标路径下,否则文件就被移除了。
断点下载
断点下载的流程如下图
断点下载原理:在网络请求之前,先获取本地已下载数据的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]; }
// 响应的过滤 - (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
@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]; }
// 进度 - (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