1.小文件下载
- 如果文件比较小,下载方式会比较多
- 直接用NSData的+ (id)dataWithContentsOfURL:(NSURL *)url;
- 利用NSURLConnection发送一个HTTP请求去下载
- 如果是下载图片,还可以利用SDWebImage框架
-(void)smallDataDownload{
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
UIImage *image = [UIImage imageWithData:data];
NSLog(@"%@",image);
}];
}
2.大文件下载(同时实现断点下载功能)
- 大文件由于需要较长时间,需要监听其具体下载过程,给用户反馈下载进度,提供良好的用户体验
- 由于用户不小心关掉程序,重新打开后,用户是希望此前的下载任务能够接着上次的下载进度继续下载的,所以这里也实现了断点下载功能
3.利用NSFileHandle实现大文件下载
- 这里先使用NSFileHandle实现文件的下载到本地
#import "ViewController.h"
#import "NSString+SandboxPath.h"
@interface ViewController ()<NSURLConnectionDataDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *progress;
@property (nonatomic, strong) NSString *path;
@property (nonatomic, assign) NSInteger totalDataSize;
@property (nonatomic, assign) NSInteger curDataLength;
@property (nonatomic, strong) NSFileHandle *fileHandle;
@end
@implementation ViewController
#pragma mark - lazy
-(NSFileHandle *)fileHandle{
if (!_fileHandle) {
_fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.path];
}
return _fileHandle;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.path = [@"minion_01.mp4" cacheDir];
NSLog(@"%@",self.path);
[self largeDataDownLoad];
}
-(void)largeDataDownLoad{
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSFileManager *manager =[NSFileManager defaultManager];
NSDictionary *dict = [manager attributesOfItemAtPath:self.path error:nil];
NSString *range = [NSString stringWithFormat:@"bytes:%zd-",[dict[NSFileSize] intValue]];
self.curDataLength = [dict[NSFileSize] intValue];
NSLog(@"%@",range);
[request setValue:range forHTTPHeaderField:@"Range"];
[NSURLConnection connectionWithRequest:request delegate:self];
}
#pragma mark - NSURLConnectionDataDelegate
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSInteger totalDataSize = response.expectedContentLength + self.curDataLength;
self.totalDataSize = totalDataSize;
NSFileManager *manager = [NSFileManager defaultManager];
if (self.curDataLength == 0) {
[manager createFileAtPath:self.path contents:nil attributes:nil];
NSLog(@"创建文件成功");
}
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
self.curDataLength += data.length;
self.progress.progress = 1.0 * self.curDataLength / self.totalDataSize;
NSLog(@"%f",self.progress.progress);
[self.fileHandle seekToEndOfFile];
[self.fileHandle writeData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(@"%s",__func__);
[self.fileHandle closeFile];
self.curDataLength = 0;
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"%s",__func__);
}
4.利用输出流实现文件的下载和断点下载(NSOutputStream)
#import "ViewController.h"
#import "NSString+SandboxPath.h"
@interface ViewController ()<NSURLConnectionDataDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *progress;
@property (nonatomic, strong) NSString *path;
@property (nonatomic, assign) NSInteger totalDataSize;
@property (nonatomic, assign) NSInteger curDataLength;
@property (nonatomic, strong) NSOutputStream *outputStream;
@end
@implementation ViewController
#pragma mark - lazy
-(NSOutputStream *)outputStream{
if (!_outputStream) {
_outputStream = [NSOutputStream outputStreamToFileAtPath:self.path append:YES];
[_outputStream open];
}
return _outputStream;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.path = [@"minion_01.mp4" cacheDir];
NSLog(@"%@",self.path);
[self largeDataDownLoad];
}
-(void)largeDataDownLoad{
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSFileManager *manager =[NSFileManager defaultManager];
NSDictionary *dict = [manager attributesOfItemAtPath:self.path error:nil];
NSString *range = [NSString stringWithFormat:@"bytes:%zd-",[dict[NSFileSize] intValue]];
self.curDataLength = [dict[NSFileSize] intValue];
NSLog(@"%@",range);
[request setValue:range forHTTPHeaderField:@"Range"];
[NSURLConnection connectionWithRequest:request delegate:self];
}
#pragma mark - NSURLConnectionDataDelegate
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSInteger totalDataSize = response.expectedContentLength + self.curDataLength;
self.totalDataSize = totalDataSize;
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.outputStream write:data.bytes maxLength:data.length];
self.curDataLength += data.length;
self.progress.progress = 1.0 * self.curDataLength / self.totalDataSize;
NSLog(@"%f",self.progress.progress);
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(@"%s",__func__);
[self.outputStream close];
self.curDataLength = 0;
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"%s",__func__);
}
5.文件上传
- 文件上传需要设置请求头和请求体的格式,并且格式固定如下格式,一步都不能少,不然无法上传成功,后面会讲解AFN框架的时候,AFN已经将这些代码封装好了,我们也无需写这些复杂的代码段
// 1.设置请求头
[request setValue:@"multipart/form-data; boundary=分割线" forHTTPHeaderField:@"Content-Type"];
// 2.设置请求体
非文件参数
--分割线
\r\n
Content-Disposition: form-data; name="参数名"
\r\n
\r\n
参数值
\r\n
文件参数
--分割线
\r\n
Content-Disposition: form-data; name="参数名"; filename="文件名"
\r\n
Content-Type: 文件的MIMEType
\r\n
\r\n
文件数据
\r\n
参数结束的标记
--分割线--
#import "ViewController.h"
// 请求头的分割线
#define ZJHeaderBoundary @"----MrRight"
// 请求体的分割线
#define ZJBoundary [@"------MrRight" dataUsingEncoding:NSUTF8StringEncoding]
// 结束符
#define ZJEndBoundary [@"------MrRight--" dataUsingEncoding:NSUTF8StringEncoding]
// 将字符串转换为二进制
#define ZJEncode(str) [str dataUsingEncoding:NSUTF8StringEncoding]
// 换行
#define ZJNewLine [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]
@interface ViewController ()
@end
@implementation ViewController
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 1.创建URL
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/upload"];
// 2.根据URL创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 2.1设置请求头
request.HTTPMethod = @"POST";
NSString *temp = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", ZJHeaderBoundary];
[request setValue:temp forHTTPHeaderField:@"Content-Type"];
// 2.2设置请求体
NSMutableData *data = [NSMutableData data];
// 拼接请求体格式
// 2.2.1设置文件参数
[data appendData:ZJBoundary];
[data appendData:ZJNewLine];
// name : 对应服务端接收的字段类型(服务端参数的名称)
// filename: 告诉服务端当前的文件的文件名称(也就是告诉服务器用什么名称保存当前上传的文件)
[data appendData:[@"Content-Disposition: form-data; name=\"file\"; filename=\"abc.png\"" dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:ZJNewLine];
// 注意点: 文件的类型不一样, 那么content-type的值也不一样
// 1.如果不知道数据时什么类型, 直接传application/octet-stream即可
// application/octet-stream是万能类型
// 数据的Content-Type类型我们称之为MIMETYPE
[data appendData:[@"Content-Type: application/octet-stream" dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:ZJNewLine];
[data appendData:ZJNewLine];
// 文件数据
UIImage *image = [UIImage imageNamed:@"abc"];
NSData *imageData = UIImagePNGRepresentation(image);
[data appendData:imageData];
[data appendData:ZJNewLine];
[data appendData:ZJNewLine];
// 2.2.2设置非文件参数
[data appendData:ZJBoundary];
[data appendData:ZJNewLine];
// name : 对应服务端接收的字段类型(服务端参数的名称)
[data appendData:[@"Content-Disposition: form-data; name=\"username\"" dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:ZJNewLine];
[data appendData:ZJNewLine];
[data appendData:[@"zj" dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:ZJNewLine];
// 2.2.3设置结束符号
[data appendData:ZJEndBoundary];
request.HTTPBody = data;
// 3.发送请求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
}
6.NSURLConnection和NSRunLoop
- 通过代理监控NSURLConnection请求,以及请求过程设置在哪个线程执行
- 通过alloc/init方法创建NSURLConnection,会自动发送异步网络请求
- 只要利用NSURLConnection发送一个请求, 那么系统会就自动将NSURLConnection加入到当前线程的RunLoop中
- 如果是在主线程中发送求情, 那么主线程的RunLoop永远存在, 所以NSURLConnection不会被释放,所以能够执行监听请求操作
- 在主线程中发送请求
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[conn start];
[conn setDelegateQueue:[[NSOperationQueue alloc] init]];
- 如果是在子线程中发送请求, 那么子线程默认没有RunLoop, 所以NSURLConnection会被释放,所以无法监听请求操作
- 所以要想在子线程中发送的请求能够被监听,若是使用alloc/init方法,就必须自己创建RunLoop对象,并且在设置监听时运行RunLoop,这样NSYRLConnection就不会被马上释放,而是会等到RunLoop执行完毕后才会释放
- 如果是通过alloc/init+startImmediately:方法调用,则不要要自己创建NSRunLoop对象,只需调用NSURLConnection的start方法,那么系统会将NSURLConnection添加到当前线程runloop的默认模式下,如果当前线程的runloop不存在, 那么系统内部会自动创建一个
- 在子线程中发送请求
-(void)sendRequestInSubThread{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[conn setDelegateQueue:[[NSOperationQueue alloc] init]];
[runloop run];
});
}